Skip to content

Commit

Permalink
PHAIN-133: Generate OpenAPI schema via CLI
Browse files Browse the repository at this point in the history
This change adds a `make generate-openapi-spec` command to generate an OpenAPI schema JSON file.
We have been using this process within the PHA API for some time.

To avoid needing to create a bunch of stub config and other misdirections during the application startup, a new startup specifically for Swagger has been added which instantiates just enough for Swagger to be able to generate the document.
  • Loading branch information
psmorgan committed Feb 14, 2025
1 parent 28f8adf commit e611a60
Show file tree
Hide file tree
Showing 14 changed files with 249 additions and 1 deletion.
10 changes: 9 additions & 1 deletion .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@
"version": "0.7.1",
"commands": [
"husky"
]
],
"rollForward": false
},
"swashbuckle.aspnetcore.cli": {
"version": "7.2.0",
"commands": [
"swagger"
],
"rollForward": false
}
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,4 @@ fabric.properties

report.csv
Btms.Backend/*.zip
openapi.json
1 change: 1 addition & 0 deletions Btms.Backend/Btms.Backend.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.8.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.8.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.8.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
</ItemGroup>

<ItemGroup>
Expand Down
49 changes: 49 additions & 0 deletions Btms.Backend/OpenApi/DocumentFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace Btms.Backend.OpenApi;

public class DocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
context.SchemaGenerator.GenerateSchema(
typeof(NotificationResourceResponse),
context.SchemaRepository
);

swaggerDoc.AddPath(
path: "import-notifications",
pathDescription: "Notification Operations",
operationDescription: "Get Notifications",
referenceId: "NotificationResourceResponse",
tag: "Notifications"
);

context.SchemaGenerator.GenerateSchema(
typeof(MovementResourceResponse),
context.SchemaRepository
);

swaggerDoc.AddPath(
path: "movements",
pathDescription: "Movement Operations",
operationDescription: "Get Movements",
referenceId: "MovementResourceResponse",
tag: "Movements"
);

context.SchemaGenerator.GenerateSchema(
typeof(GoodsMovementResourceResponse),
context.SchemaRepository
);

swaggerDoc.AddPath(
path: "goods-movements",
pathDescription: "Goods Movement Operations",
operationDescription: "Get Goods Movements",
referenceId: "GoodsMovementResourceResponse",
tag: "GoodsMovements"
);
}
}
5 changes: 5 additions & 0 deletions Btms.Backend/OpenApi/GoodsMovementResourceResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Btms.Model.Gvms;

namespace Btms.Backend.OpenApi;

public class GoodsMovementResourceResponse : ResourceResponse<Gmr>;
5 changes: 5 additions & 0 deletions Btms.Backend/OpenApi/MovementResourceResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Btms.Model;

namespace Btms.Backend.OpenApi;

public class MovementResourceResponse : ResourceResponse<Movement>;
5 changes: 5 additions & 0 deletions Btms.Backend/OpenApi/NotificationResourceResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Btms.Model.Ipaffs;

namespace Btms.Backend.OpenApi;

public class NotificationResourceResponse : ResourceResponse<ImportNotification>;
62 changes: 62 additions & 0 deletions Btms.Backend/OpenApi/OpenApiDocumentExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Microsoft.OpenApi.Models;

namespace Btms.Backend.OpenApi;

public static class OpenApiDocumentExtensions
{
public static void AddPath(
this OpenApiDocument swaggerDoc,
string path,
string pathDescription,
string operationDescription,
string referenceId,
string tag
)
{
swaggerDoc.Paths.Add(
path,
new OpenApiPathItem
{
Description = pathDescription,
Operations = new Dictionary<OperationType, OpenApiOperation>
{
{
OperationType.Get,
new OpenApiOperation
{
Description = operationDescription,
Responses = new OpenApiResponses
{
{
"200",
new OpenApiResponse
{
Description = "Success",
Content = new Dictionary<string, OpenApiMediaType>
{
{
"application/json",
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Id = referenceId,
Type = ReferenceType.Schema,
},
},
}
},
},
}
},
},
Tags = new List<OpenApiTag> { new() { Name = tag } },
}
},
},
}
);
}
}
6 changes: 6 additions & 0 deletions Btms.Backend/OpenApi/ResourceResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Btms.Backend.OpenApi;

public class ResourceResponse<T>
{
public T? Data { get; set; }
}
56 changes: 56 additions & 0 deletions Btms.Backend/OpenApi/SchemaFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.ComponentModel;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using MongoDB.Bson.Serialization.Attributes;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace Btms.Backend.OpenApi;

public class SchemaFilter : ISchemaFilter
{
private readonly JsonNamingPolicy _namingPolicy = JsonNamingPolicy.CamelCase;

public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (IsBsonIgnoreField(schema, context))
return;

foreach (var propertyInfo in context.Type.GetProperties())
{
if (HasBsonIgnoreAttribute(propertyInfo))
schema.Properties.Remove(GetPropertyName(propertyInfo));
}

schema.Enum = GetEnums(context);
}

private static IList<IOpenApiAny> GetEnums(SchemaFilterContext context)
{
var enumOpenApiStrings = new List<IOpenApiAny>();
if (!context.Type.IsEnum) return enumOpenApiStrings;

enumOpenApiStrings.AddRange((from object? enumValue in Enum.GetValues(context.Type) select new OpenApiString(enumValue.ToString())).Cast<IOpenApiAny>());

return enumOpenApiStrings;
}

private string GetPropertyName(PropertyInfo propertyInfo)
{
var jsonAttr = propertyInfo.GetCustomAttribute<JsonPropertyNameAttribute>();
return _namingPolicy.ConvertName(jsonAttr != null ? jsonAttr.Name : propertyInfo.Name);
}
private static bool HasBsonIgnoreAttribute(PropertyInfo propertyInfo)
{
return propertyInfo.GetCustomAttribute(typeof(BsonIgnoreAttribute)) != null;
}

private static bool IsBsonIgnoreField(OpenApiSchema schema, SchemaFilterContext context)
{
return schema.Properties == null || context.Type == null;
}


}
4 changes: 4 additions & 0 deletions Btms.Backend/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,14 @@
using Environment = System.Environment;
using Btms.Backend.Asb;
using Btms.Business.Mediatr;
using Btms.Backend.Swagger;
using Btms.Common;

//-------- Configure the WebApplication builder------------------//

if (SwaggerGen.SwaggerGenEntrypoint(args))
return;

var app = CreateWebApplication(args);
await app.RunAsync();

Expand Down
34 changes: 34 additions & 0 deletions Btms.Backend/Swagger/SwaggerGen.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Reflection;
using Btms.Backend.OpenApi;
using Microsoft.OpenApi.Models;

namespace Btms.Backend.Swagger;

public static class SwaggerGen
{
public static bool SwaggerGenEntrypoint(string[] args)
{
if (Assembly.GetEntryAssembly()?.GetName().Name != "dotnet-swagger") return false;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("public-v0.1", new OpenApiInfo { Title = "BTMS Public API", Version = "v0.1" });
c.DocumentFilter<DocumentFilter>();
c.SchemaFilter<SchemaFilter>();
c.UseAllOfToExtendReferenceSchemas();
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
});

var appForGen = builder.Build();
appForGen.UseSwagger();
appForGen.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/swagger/public-v0.1/swagger.json", "public");
});

appForGen.Run();
return true;
}
}
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
dependencies:
dotnet tool restore

generate-openapi-spec: dependencies
dotnet build -c Release --no-restore
dotnet swagger tofile --output openapi.json ./Btms.Backend/bin/Release/net*/Btms.Backend.dll public-v0.1
6 changes: 6 additions & 0 deletions global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "8.0.405",
"rollForward": "latestMinor"
}
}

0 comments on commit e611a60

Please sign in to comment.