From 7309858016bf87c36d094b680c24f35884bdd881 Mon Sep 17 00:00:00 2001 From: Aleksandr Vishniakov Date: Wed, 17 Jun 2020 06:38:37 +0200 Subject: [PATCH 1/3] Fix API schema issues --- .../Swagger/ApiSchemaOptions.cs | 13 ++++++ .../Swagger/ApiSchemaVersion.cs | 8 ++++ .../Swagger/NameAndOrderRequestBodyFilter.cs | 36 ++++++++++++++++ .../Swagger/NewtonsoftJsonIgnoreFilter.cs | 24 ----------- .../Swagger/OpenApiSpecificationVersion.cs | 8 ++++ .../Swagger/OptionalParametersFilter.cs | 31 -------------- .../Swagger/ParameterOrderFilter.cs | 42 +++++++++++++++++++ .../Swagger/RequireRequestBodyFilter.cs | 13 ++++++ .../Infrastructure/Swagger/SwaggerOptions.cs | 7 ++++ VirtoCommerce.Storefront/Startup.cs | 18 ++++++-- .../VirtoCommerce.Storefront.csproj | 1 + VirtoCommerce.Storefront/appsettings.json | 6 +++ 12 files changed, 148 insertions(+), 59 deletions(-) create mode 100644 VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaOptions.cs create mode 100644 VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaVersion.cs create mode 100644 VirtoCommerce.Storefront/Infrastructure/Swagger/NameAndOrderRequestBodyFilter.cs delete mode 100644 VirtoCommerce.Storefront/Infrastructure/Swagger/NewtonsoftJsonIgnoreFilter.cs create mode 100644 VirtoCommerce.Storefront/Infrastructure/Swagger/OpenApiSpecificationVersion.cs delete mode 100644 VirtoCommerce.Storefront/Infrastructure/Swagger/OptionalParametersFilter.cs create mode 100644 VirtoCommerce.Storefront/Infrastructure/Swagger/ParameterOrderFilter.cs create mode 100644 VirtoCommerce.Storefront/Infrastructure/Swagger/RequireRequestBodyFilter.cs create mode 100644 VirtoCommerce.Storefront/Infrastructure/Swagger/SwaggerOptions.cs diff --git a/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaOptions.cs b/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaOptions.cs new file mode 100644 index 0000000..d9b66ba --- /dev/null +++ b/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaOptions.cs @@ -0,0 +1,13 @@ +using System; + +namespace VirtoCommerce.Storefront.Infrastructure.Swagger +{ + public class ApiSchemaOptions + { + public bool NameAndOrderRequestBody { get; set; } + + public bool NotNullableReferenceTypesInArrays { get; set; } + + public OpenApiSpecificationVersion OpenApiSpecificationVersion { get; set; } + } +} diff --git a/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaVersion.cs b/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaVersion.cs new file mode 100644 index 0000000..7775d47 --- /dev/null +++ b/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaVersion.cs @@ -0,0 +1,8 @@ +namespace VirtoCommerce.Storefront.Infrastructure.Swagger +{ + public enum ApiSchemaVersion + { + V1, + V2 + } +} diff --git a/VirtoCommerce.Storefront/Infrastructure/Swagger/NameAndOrderRequestBodyFilter.cs b/VirtoCommerce.Storefront/Infrastructure/Swagger/NameAndOrderRequestBodyFilter.cs new file mode 100644 index 0000000..d08740b --- /dev/null +++ b/VirtoCommerce.Storefront/Infrastructure/Swagger/NameAndOrderRequestBodyFilter.cs @@ -0,0 +1,36 @@ +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace VirtoCommerce.Storefront.Infrastructure.Swagger +{ + public class NameAndOrderRequestBodyFilter: IRequestBodyFilter + { + private readonly SwaggerOptions _swaggerOptions; + + public NameAndOrderRequestBodyFilter(IOptions swaggerOptions) + { + _swaggerOptions = swaggerOptions.Value; + } + + public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext context) + { + // Even if it's v2 or request body shouldn't be named + // we want name it because of bugs and different approaches in generation tools + var bodyName = _swaggerOptions.Schema.NameAndOrderRequestBody + ? context.BodyParameterDescription.Name + : "body"; + requestBody.Extensions.Add("x-name", new OpenApiString(bodyName)); + requestBody.Extensions.Add("x-codegen-request-body-name", new OpenApiString(bodyName)); + requestBody.Extensions.Add("x-ms-requestBody-name", new OpenApiString(bodyName)); + + if (_swaggerOptions.Schema.NameAndOrderRequestBody) + { + var bodyPosition = context.BodyParameterDescription.ParameterInfo().Position; + requestBody.Extensions.Add("x-position", new OpenApiInteger(bodyPosition)); + requestBody.Extensions.Add("x-ms-requestBody-index", new OpenApiInteger(bodyPosition)); + } + } + } +} diff --git a/VirtoCommerce.Storefront/Infrastructure/Swagger/NewtonsoftJsonIgnoreFilter.cs b/VirtoCommerce.Storefront/Infrastructure/Swagger/NewtonsoftJsonIgnoreFilter.cs deleted file mode 100644 index 63b1297..0000000 --- a/VirtoCommerce.Storefront/Infrastructure/Swagger/NewtonsoftJsonIgnoreFilter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Linq; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace VirtoCommerce.Storefront.Infrastructure.Swagger -{ - /// - /// Allows to ignore . - /// - public class NewtonsoftJsonIgnoreFilter : ISchemaFilter - { - public void Apply(OpenApiSchema schema, SchemaFilterContext context) - { - var type = context.Type; - foreach (var prop in type.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance) - .Where(p => p.GetCustomAttributes(typeof(Newtonsoft.Json.JsonIgnoreAttribute), true)?.Any() == true)) - { - var propName = prop.Name[0].ToString().ToLower() + prop.Name.Substring(1); - if (schema?.Properties?.ContainsKey(propName) == true) - schema?.Properties?.Remove(propName); - } - } - } -} diff --git a/VirtoCommerce.Storefront/Infrastructure/Swagger/OpenApiSpecificationVersion.cs b/VirtoCommerce.Storefront/Infrastructure/Swagger/OpenApiSpecificationVersion.cs new file mode 100644 index 0000000..488f5c7 --- /dev/null +++ b/VirtoCommerce.Storefront/Infrastructure/Swagger/OpenApiSpecificationVersion.cs @@ -0,0 +1,8 @@ +namespace VirtoCommerce.Storefront.Infrastructure.Swagger +{ + public enum OpenApiSpecificationVersion + { + V2, + V3 + } +} diff --git a/VirtoCommerce.Storefront/Infrastructure/Swagger/OptionalParametersFilter.cs b/VirtoCommerce.Storefront/Infrastructure/Swagger/OptionalParametersFilter.cs deleted file mode 100644 index d27443a..0000000 --- a/VirtoCommerce.Storefront/Infrastructure/Swagger/OptionalParametersFilter.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Linq; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; - -namespace VirtoCommerce.Storefront.Infrastructure.Swagger -{ - public class OptionalParametersFilter : IOperationFilter - { - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - if (operation.Parameters == null || !operation.Parameters.Any()) - { - return; - } - - var optionalParameters = context.ApiDescription.ParameterDescriptions - .Where(p => p.ParameterDescriptor != null && - ((ControllerParameterDescriptor)p.ParameterDescriptor).ParameterInfo.CustomAttributes.Any(attr => attr.AttributeType == typeof(SwaggerOptionalAttribute))).ToList(); - - foreach (var apiParameter in optionalParameters) - { - var parameter = operation.Parameters.FirstOrDefault(p => p.Name == apiParameter.Name); - if (parameter != null) - { - parameter.Required = false; - } - } - } - } -} diff --git a/VirtoCommerce.Storefront/Infrastructure/Swagger/ParameterOrderFilter.cs b/VirtoCommerce.Storefront/Infrastructure/Swagger/ParameterOrderFilter.cs new file mode 100644 index 0000000..43ee875 --- /dev/null +++ b/VirtoCommerce.Storefront/Infrastructure/Swagger/ParameterOrderFilter.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.Options; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace VirtoCommerce.Storefront.Infrastructure.Swagger +{ + public class ParameterOrderFilter: IParameterFilter + { + private SwaggerOptions _swaggerOptions; + + public ParameterOrderFilter(IOptions swaggerOptions) + { + _swaggerOptions = swaggerOptions.Value; + } + + public void Apply(OpenApiParameter parameter, ParameterFilterContext context) + { + if (_swaggerOptions.Schema.NameAndOrderRequestBody) + { + // Explicitly specify parameters position + // Position for store and language is unknown and they should be last, so use int.MaxValue relative values + if (context.ParameterInfo != null) + { + parameter.Extensions.Add("x-position", new OpenApiInteger(context.ParameterInfo.Position)); + } + else + { + switch (parameter.Name) + { + case "store": + parameter.Extensions.Add("x-position", new OpenApiInteger(int.MaxValue - 1)); + break; + case "language": + parameter.Extensions.Add("x-position", new OpenApiInteger(int.MaxValue)); + break; + } + } + } + } + } +} diff --git a/VirtoCommerce.Storefront/Infrastructure/Swagger/RequireRequestBodyFilter.cs b/VirtoCommerce.Storefront/Infrastructure/Swagger/RequireRequestBodyFilter.cs new file mode 100644 index 0000000..40754f1 --- /dev/null +++ b/VirtoCommerce.Storefront/Infrastructure/Swagger/RequireRequestBodyFilter.cs @@ -0,0 +1,13 @@ +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace VirtoCommerce.Storefront.Infrastructure.Swagger +{ + public class RequireRequestBodyFilter: IRequestBodyFilter + { + public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext context) + { + requestBody.Required = true; + } + } +} diff --git a/VirtoCommerce.Storefront/Infrastructure/Swagger/SwaggerOptions.cs b/VirtoCommerce.Storefront/Infrastructure/Swagger/SwaggerOptions.cs new file mode 100644 index 0000000..a46d59c --- /dev/null +++ b/VirtoCommerce.Storefront/Infrastructure/Swagger/SwaggerOptions.cs @@ -0,0 +1,7 @@ +namespace VirtoCommerce.Storefront.Infrastructure.Swagger +{ + public class SwaggerOptions + { + public ApiSchemaOptions Schema { get; set; } + } +} diff --git a/VirtoCommerce.Storefront/Startup.cs b/VirtoCommerce.Storefront/Startup.cs index 272954c..b01977a 100644 --- a/VirtoCommerce.Storefront/Startup.cs +++ b/VirtoCommerce.Storefront/Startup.cs @@ -346,21 +346,25 @@ public void ConfigureServices(IServiceCollection services) options.MaxAge = TimeSpan.FromDays(30); }); + services.Configure(Configuration.GetSection("Swagger").Bind); + // Register the Swagger generator, defining 1 or more Swagger documents services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Storefront REST API documentation", Version = "v1" }); c.IgnoreObsoleteProperties(); c.IgnoreObsoleteActions(); + c.ParameterFilter(); + c.RequestBodyFilter(); + c.RequestBodyFilter(); // To include 401 response type to actions that requires Authorization c.OperationFilter(); c.OperationFilter(); - c.OperationFilter(); - c.OperationFilter(); c.OperationFilter(); + c.OperationFilter(); c.OperationFilter(); c.SchemaFilter(); - c.SchemaFilter(); + c.SchemaGeneratorOptions.UseAllOfToExtendReferenceSchemas = true; // Use method name as operation ID, i.e. ApiAccount.GetOrganization instead of /storefrontapi/account/organization (will be treated as just organization method) c.CustomOperationIds(apiDesc => apiDesc.TryGetMethodInfo(out var methodInfo) ? methodInfo.Name : null); @@ -368,6 +372,7 @@ public void ConfigureServices(IServiceCollection services) // To avoid errors with repeating type names c.CustomSchemaIds(type => (Attribute.GetCustomAttribute(type, typeof(SwaggerSchemaIdAttribute)) as SwaggerSchemaIdAttribute)?.Id ?? type.FriendlyId()); }); + services.AddSwaggerGenNewtonsoftSupport(); services.AddResponseCompression(); @@ -421,7 +426,12 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) }); // Enable middleware to serve generated Swagger as a JSON endpoint. - app.UseSwagger(c => c.RouteTemplate = "docs/{documentName}/docs.json"); + app.UseSwagger(c => + { + var options = app.ApplicationServices.GetService>().Value; + c.SerializeAsV2 = options.Schema.OpenApiSpecificationVersion == OpenApiSpecificationVersion.V2; + c.RouteTemplate = "docs/{documentName}/docs.json"; + }); var rewriteOptions = new RewriteOptions(); // Load IIS url rewrite rules from external file diff --git a/VirtoCommerce.Storefront/VirtoCommerce.Storefront.csproj b/VirtoCommerce.Storefront/VirtoCommerce.Storefront.csproj index 6570c13..a29ca26 100644 --- a/VirtoCommerce.Storefront/VirtoCommerce.Storefront.csproj +++ b/VirtoCommerce.Storefront/VirtoCommerce.Storefront.csproj @@ -61,6 +61,7 @@ + diff --git a/VirtoCommerce.Storefront/appsettings.json b/VirtoCommerce.Storefront/appsettings.json index 37d329c..6eb4a0f 100644 --- a/VirtoCommerce.Storefront/appsettings.json +++ b/VirtoCommerce.Storefront/appsettings.json @@ -91,5 +91,11 @@ "SnapshotInLowPriorityThread": true, "ProvideAnonymousTelemetry": true, "FailedRequestLimit": 3 + }, + "Swagger": { + "Schema": { + "NameAndOrderRequestBody": true, + "OpenApiSpecificationVersion": "v3" + } } } From 2eb7e7fbc1352b22f46c6e9473b602e303243d3b Mon Sep 17 00:00:00 2001 From: Aleksandr Vishniakov Date: Wed, 17 Jun 2020 07:24:47 +0200 Subject: [PATCH 2/3] Remove unuse parameter --- .../Infrastructure/Swagger/ApiSchemaOptions.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaOptions.cs b/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaOptions.cs index d9b66ba..4fa81e8 100644 --- a/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaOptions.cs +++ b/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaOptions.cs @@ -6,8 +6,6 @@ public class ApiSchemaOptions { public bool NameAndOrderRequestBody { get; set; } - public bool NotNullableReferenceTypesInArrays { get; set; } - public OpenApiSpecificationVersion OpenApiSpecificationVersion { get; set; } } } From 0296d007c73c0af3421238a3ec4090d75bebe630 Mon Sep 17 00:00:00 2001 From: Aleksandr Vishniakov Date: Fri, 26 Jun 2020 11:22:35 +0200 Subject: [PATCH 3/3] Fix code smells --- .../Infrastructure/Swagger/ApiSchemaOptions.cs | 2 -- .../Infrastructure/Swagger/ParameterOrderFilter.cs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaOptions.cs b/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaOptions.cs index 4fa81e8..2d10900 100644 --- a/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaOptions.cs +++ b/VirtoCommerce.Storefront/Infrastructure/Swagger/ApiSchemaOptions.cs @@ -1,5 +1,3 @@ -using System; - namespace VirtoCommerce.Storefront.Infrastructure.Swagger { public class ApiSchemaOptions diff --git a/VirtoCommerce.Storefront/Infrastructure/Swagger/ParameterOrderFilter.cs b/VirtoCommerce.Storefront/Infrastructure/Swagger/ParameterOrderFilter.cs index 43ee875..d93b550 100644 --- a/VirtoCommerce.Storefront/Infrastructure/Swagger/ParameterOrderFilter.cs +++ b/VirtoCommerce.Storefront/Infrastructure/Swagger/ParameterOrderFilter.cs @@ -7,7 +7,7 @@ namespace VirtoCommerce.Storefront.Infrastructure.Swagger { public class ParameterOrderFilter: IParameterFilter { - private SwaggerOptions _swaggerOptions; + private readonly SwaggerOptions _swaggerOptions; public ParameterOrderFilter(IOptions swaggerOptions) {