Skip to content

Commit 7309858

Browse files
committed
Fix API schema issues
1 parent 404414f commit 7309858

12 files changed

+148
-59
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
3+
namespace VirtoCommerce.Storefront.Infrastructure.Swagger
4+
{
5+
public class ApiSchemaOptions
6+
{
7+
public bool NameAndOrderRequestBody { get; set; }
8+
9+
public bool NotNullableReferenceTypesInArrays { get; set; }
10+
11+
public OpenApiSpecificationVersion OpenApiSpecificationVersion { get; set; }
12+
}
13+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace VirtoCommerce.Storefront.Infrastructure.Swagger
2+
{
3+
public enum ApiSchemaVersion
4+
{
5+
V1,
6+
V2
7+
}
8+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using Microsoft.Extensions.Options;
2+
using Microsoft.OpenApi.Any;
3+
using Microsoft.OpenApi.Models;
4+
using Swashbuckle.AspNetCore.SwaggerGen;
5+
6+
namespace VirtoCommerce.Storefront.Infrastructure.Swagger
7+
{
8+
public class NameAndOrderRequestBodyFilter: IRequestBodyFilter
9+
{
10+
private readonly SwaggerOptions _swaggerOptions;
11+
12+
public NameAndOrderRequestBodyFilter(IOptions<SwaggerOptions> swaggerOptions)
13+
{
14+
_swaggerOptions = swaggerOptions.Value;
15+
}
16+
17+
public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext context)
18+
{
19+
// Even if it's v2 or request body shouldn't be named
20+
// we want name it because of bugs and different approaches in generation tools
21+
var bodyName = _swaggerOptions.Schema.NameAndOrderRequestBody
22+
? context.BodyParameterDescription.Name
23+
: "body";
24+
requestBody.Extensions.Add("x-name", new OpenApiString(bodyName));
25+
requestBody.Extensions.Add("x-codegen-request-body-name", new OpenApiString(bodyName));
26+
requestBody.Extensions.Add("x-ms-requestBody-name", new OpenApiString(bodyName));
27+
28+
if (_swaggerOptions.Schema.NameAndOrderRequestBody)
29+
{
30+
var bodyPosition = context.BodyParameterDescription.ParameterInfo().Position;
31+
requestBody.Extensions.Add("x-position", new OpenApiInteger(bodyPosition));
32+
requestBody.Extensions.Add("x-ms-requestBody-index", new OpenApiInteger(bodyPosition));
33+
}
34+
}
35+
}
36+
}

VirtoCommerce.Storefront/Infrastructure/Swagger/NewtonsoftJsonIgnoreFilter.cs

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace VirtoCommerce.Storefront.Infrastructure.Swagger
2+
{
3+
public enum OpenApiSpecificationVersion
4+
{
5+
V2,
6+
V3
7+
}
8+
}

VirtoCommerce.Storefront/Infrastructure/Swagger/OptionalParametersFilter.cs

Lines changed: 0 additions & 31 deletions
This file was deleted.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using Microsoft.Extensions.Options;
2+
using Microsoft.OpenApi.Any;
3+
using Microsoft.OpenApi.Models;
4+
using Swashbuckle.AspNetCore.SwaggerGen;
5+
6+
namespace VirtoCommerce.Storefront.Infrastructure.Swagger
7+
{
8+
public class ParameterOrderFilter: IParameterFilter
9+
{
10+
private SwaggerOptions _swaggerOptions;
11+
12+
public ParameterOrderFilter(IOptions<SwaggerOptions> swaggerOptions)
13+
{
14+
_swaggerOptions = swaggerOptions.Value;
15+
}
16+
17+
public void Apply(OpenApiParameter parameter, ParameterFilterContext context)
18+
{
19+
if (_swaggerOptions.Schema.NameAndOrderRequestBody)
20+
{
21+
// Explicitly specify parameters position
22+
// Position for store and language is unknown and they should be last, so use int.MaxValue relative values
23+
if (context.ParameterInfo != null)
24+
{
25+
parameter.Extensions.Add("x-position", new OpenApiInteger(context.ParameterInfo.Position));
26+
}
27+
else
28+
{
29+
switch (parameter.Name)
30+
{
31+
case "store":
32+
parameter.Extensions.Add("x-position", new OpenApiInteger(int.MaxValue - 1));
33+
break;
34+
case "language":
35+
parameter.Extensions.Add("x-position", new OpenApiInteger(int.MaxValue));
36+
break;
37+
}
38+
}
39+
}
40+
}
41+
}
42+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Microsoft.OpenApi.Models;
2+
using Swashbuckle.AspNetCore.SwaggerGen;
3+
4+
namespace VirtoCommerce.Storefront.Infrastructure.Swagger
5+
{
6+
public class RequireRequestBodyFilter: IRequestBodyFilter
7+
{
8+
public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext context)
9+
{
10+
requestBody.Required = true;
11+
}
12+
}
13+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace VirtoCommerce.Storefront.Infrastructure.Swagger
2+
{
3+
public class SwaggerOptions
4+
{
5+
public ApiSchemaOptions Schema { get; set; }
6+
}
7+
}

VirtoCommerce.Storefront/Startup.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -346,28 +346,33 @@ public void ConfigureServices(IServiceCollection services)
346346
options.MaxAge = TimeSpan.FromDays(30);
347347
});
348348

349+
services.Configure<SwaggerOptions>(Configuration.GetSection("Swagger").Bind);
350+
349351
// Register the Swagger generator, defining 1 or more Swagger documents
350352
services.AddSwaggerGen(c =>
351353
{
352354
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Storefront REST API documentation", Version = "v1" });
353355
c.IgnoreObsoleteProperties();
354356
c.IgnoreObsoleteActions();
357+
c.ParameterFilter<ParameterOrderFilter>();
358+
c.RequestBodyFilter<NameAndOrderRequestBodyFilter>();
359+
c.RequestBodyFilter<RequireRequestBodyFilter>();
355360
// To include 401 response type to actions that requires Authorization
356361
c.OperationFilter<AuthResponsesOperationFilter>();
357362
c.OperationFilter<ConsumeFromBodyFilter>();
358-
c.OperationFilter<FileResponseTypeFilter>();
359-
c.OperationFilter<OptionalParametersFilter>();
360363
c.OperationFilter<ArrayInQueryParametersFilter>();
364+
c.OperationFilter<FileResponseTypeFilter>();
361365
c.OperationFilter<FileUploadOperationFilter>();
362366
c.SchemaFilter<EnumSchemaFilter>();
363-
c.SchemaFilter<NewtonsoftJsonIgnoreFilter>();
367+
c.SchemaGeneratorOptions.UseAllOfToExtendReferenceSchemas = true;
364368

365369
// Use method name as operation ID, i.e. ApiAccount.GetOrganization instead of /storefrontapi/account/organization (will be treated as just organization method)
366370
c.CustomOperationIds(apiDesc => apiDesc.TryGetMethodInfo(out var methodInfo) ? methodInfo.Name : null);
367371

368372
// To avoid errors with repeating type names
369373
c.CustomSchemaIds(type => (Attribute.GetCustomAttribute(type, typeof(SwaggerSchemaIdAttribute)) as SwaggerSchemaIdAttribute)?.Id ?? type.FriendlyId());
370374
});
375+
services.AddSwaggerGenNewtonsoftSupport();
371376

372377
services.AddResponseCompression();
373378

@@ -421,7 +426,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
421426
});
422427

423428
// Enable middleware to serve generated Swagger as a JSON endpoint.
424-
app.UseSwagger(c => c.RouteTemplate = "docs/{documentName}/docs.json");
429+
app.UseSwagger(c =>
430+
{
431+
var options = app.ApplicationServices.GetService<IOptions<SwaggerOptions>>().Value;
432+
c.SerializeAsV2 = options.Schema.OpenApiSpecificationVersion == OpenApiSpecificationVersion.V2;
433+
c.RouteTemplate = "docs/{documentName}/docs.json";
434+
});
425435

426436
var rewriteOptions = new RewriteOptions();
427437
// Load IIS url rewrite rules from external file

0 commit comments

Comments
 (0)