diff --git a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsDocumentFilter.cs b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsDocumentFilter.cs index 8af6b04c08..a54ed0e838 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsDocumentFilter.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsDocumentFilter.cs @@ -8,8 +8,7 @@ public class AnnotationsDocumentFilter : IDocumentFilter { public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { - if (swaggerDoc.Tags == null) - swaggerDoc.Tags = new List(); + swaggerDoc.Tags ??= []; // Collect (unique) controller names and custom attributes in a dictionary var controllerNamesAndAttributes = context.ApiDescriptions @@ -24,7 +23,7 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) } } - private void ApplySwaggerTagAttribute( + private static void ApplySwaggerTagAttribute( OpenApiDocument swaggerDoc, string controllerName, IEnumerable customAttributes) @@ -33,7 +32,10 @@ private void ApplySwaggerTagAttribute( .OfType() .FirstOrDefault(); - if (swaggerTagAttribute == null) return; + if (swaggerTagAttribute == null) + { + return; + } swaggerDoc.Tags.Add(new OpenApiTag { diff --git a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsOperationFilter.cs b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsOperationFilter.cs index b0bded559f..5ed95a8e64 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsOperationFilter.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsOperationFilter.cs @@ -11,16 +11,16 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context) IEnumerable actionAttributes = []; IEnumerable metadataAttributes = []; - if (context.MethodInfo != null) + if (context.MethodInfo is { } methodInfo) { - controllerAttributes = context.MethodInfo.DeclaringType.GetCustomAttributes(true); - actionAttributes = context.MethodInfo.GetCustomAttributes(true); + controllerAttributes = methodInfo.DeclaringType.GetCustomAttributes(true); + actionAttributes = methodInfo.GetCustomAttributes(true); } #if NET - if (context.ApiDescription?.ActionDescriptor?.EndpointMetadata != null) + if (context.ApiDescription?.ActionDescriptor?.EndpointMetadata is { } metadata) { - metadataAttributes = context.ApiDescription.ActionDescriptor.EndpointMetadata; + metadataAttributes = metadata; } #endif @@ -49,20 +49,29 @@ private static void ApplySwaggerOperationAttribute( .OfType() .FirstOrDefault(); - if (swaggerOperationAttribute == null) return; + if (swaggerOperationAttribute == null) + { + return; + } - if (swaggerOperationAttribute.Summary != null) - operation.Summary = swaggerOperationAttribute.Summary; + if (swaggerOperationAttribute.Summary is { } summary) + { + operation.Summary = summary; + } - if (swaggerOperationAttribute.Description != null) - operation.Description = swaggerOperationAttribute.Description; + if (swaggerOperationAttribute.Description is { } description) + { + operation.Description = description; + } - if (swaggerOperationAttribute.OperationId != null) - operation.OperationId = swaggerOperationAttribute.OperationId; + if (swaggerOperationAttribute.OperationId is { } operationId) + { + operation.OperationId = operationId; + } - if (swaggerOperationAttribute.Tags != null) + if (swaggerOperationAttribute.Tags is { } tags) { - operation.Tags = [.. swaggerOperationAttribute.Tags.Select(tagName => new OpenApiTag { Name = tagName })]; + operation.Tags = [.. tags.Select(tagName => new OpenApiTag { Name = tagName })]; } } @@ -99,18 +108,18 @@ private static void ApplySwaggerResponseAttributes( response = new OpenApiResponse(); } - if (swaggerResponseAttribute.Description != null) + if (swaggerResponseAttribute.Description is { } description) { - response.Description = swaggerResponseAttribute.Description; + response.Description = description; } operation.Responses[statusCode] = response; - if (swaggerResponseAttribute.ContentTypes != null) + if (swaggerResponseAttribute.ContentTypes is { } contentTypes) { response.Content.Clear(); - foreach (var contentType in swaggerResponseAttribute.ContentTypes) + foreach (var contentType in contentTypes) { var schema = (swaggerResponseAttribute.Type != null && swaggerResponseAttribute.Type != typeof(void)) ? context.SchemaGenerator.GenerateSchema(swaggerResponseAttribute.Type, context.SchemaRepository) diff --git a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsParameterFilter.cs b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsParameterFilter.cs index e2741c5606..d9926885a2 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsParameterFilter.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsParameterFilter.cs @@ -18,30 +18,37 @@ public void Apply(OpenApiParameter parameter, ParameterFilterContext context) } } - private void ApplyPropertyAnnotations(OpenApiParameter parameter, PropertyInfo propertyInfo) + private static void ApplyPropertyAnnotations(OpenApiParameter parameter, PropertyInfo propertyInfo) { var swaggerParameterAttribute = propertyInfo.GetCustomAttributes() .FirstOrDefault(); if (swaggerParameterAttribute != null) + { ApplySwaggerParameterAttribute(parameter, swaggerParameterAttribute); + } } - private void ApplyParamAnnotations(OpenApiParameter parameter, ParameterInfo parameterInfo) + private static void ApplyParamAnnotations(OpenApiParameter parameter, ParameterInfo parameterInfo) { - var swaggerParameterAttribute = parameterInfo.GetCustomAttribute(); if (swaggerParameterAttribute != null) + { ApplySwaggerParameterAttribute(parameter, swaggerParameterAttribute); + } } - private void ApplySwaggerParameterAttribute(OpenApiParameter parameter, SwaggerParameterAttribute swaggerParameterAttribute) + private static void ApplySwaggerParameterAttribute(OpenApiParameter parameter, SwaggerParameterAttribute swaggerParameterAttribute) { if (swaggerParameterAttribute.Description != null) + { parameter.Description = swaggerParameterAttribute.Description; + } if (swaggerParameterAttribute.RequiredFlag.HasValue) + { parameter.Required = swaggerParameterAttribute.RequiredFlag.Value; + } } } diff --git a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsRequestBodyFilter.cs b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsRequestBodyFilter.cs index 89b7175f29..eb8cfd118a 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsRequestBodyFilter.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsRequestBodyFilter.cs @@ -10,7 +10,10 @@ public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext conte { var bodyParameterDescription = context.BodyParameterDescription; - if (bodyParameterDescription == null) return; + if (bodyParameterDescription == null) + { + return; + } var propertyInfo = bodyParameterDescription.PropertyInfo(); if (propertyInfo != null) @@ -33,7 +36,9 @@ private void ApplyPropertyAnnotations(OpenApiRequestBody parameter, PropertyInfo .FirstOrDefault(); if (swaggerRequestBodyAttribute != null) + { ApplySwaggerRequestBodyAttribute(parameter, swaggerRequestBodyAttribute); + } } private void ApplyParamAnnotations(OpenApiRequestBody requestBody, ParameterInfo parameterInfo) @@ -41,15 +46,21 @@ private void ApplyParamAnnotations(OpenApiRequestBody requestBody, ParameterInfo var swaggerRequestBodyAttribute = parameterInfo.GetCustomAttribute(); if (swaggerRequestBodyAttribute != null) + { ApplySwaggerRequestBodyAttribute(requestBody, swaggerRequestBodyAttribute); + } } private void ApplySwaggerRequestBodyAttribute(OpenApiRequestBody parameter, SwaggerRequestBodyAttribute swaggerRequestBodyAttribute) { if (swaggerRequestBodyAttribute.Description != null) + { parameter.Description = swaggerRequestBodyAttribute.Description; + } if (swaggerRequestBodyAttribute.RequiredFlag.HasValue) + { parameter.Required = swaggerRequestBodyAttribute.RequiredFlag.Value; + } } } diff --git a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsSchemaFilter.cs b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsSchemaFilter.cs index 3aa24c5fad..59afc4fada 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsSchemaFilter.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsSchemaFilter.cs @@ -5,14 +5,9 @@ namespace Swashbuckle.AspNetCore.Annotations; -public class AnnotationsSchemaFilter : ISchemaFilter +public class AnnotationsSchemaFilter(IServiceProvider serviceProvider) : ISchemaFilter { - private readonly IServiceProvider _serviceProvider; - - public AnnotationsSchemaFilter(IServiceProvider serviceProvider) - { - _serviceProvider = serviceProvider; - } + private readonly IServiceProvider _serviceProvider = serviceProvider; public void Apply(OpenApiSchema schema, SchemaFilterContext context) { @@ -37,7 +32,9 @@ private void ApplyTypeAnnotations(OpenApiSchema schema, SchemaFilterContext cont .FirstOrDefault(); if (schemaAttribute != null) + { ApplySchemaAttribute(schema, schemaAttribute); + } var schemaFilterAttribute = context.Type.GetCustomAttributes() .FirstOrDefault(); @@ -53,45 +50,63 @@ private void ApplyTypeAnnotations(OpenApiSchema schema, SchemaFilterContext cont } } - private void ApplyParamAnnotations(OpenApiSchema schema, ParameterInfo parameterInfo) + private static void ApplyParamAnnotations(OpenApiSchema schema, ParameterInfo parameterInfo) { var schemaAttribute = parameterInfo.GetCustomAttributes() .FirstOrDefault(); if (schemaAttribute != null) + { ApplySchemaAttribute(schema, schemaAttribute); + } } - private void ApplyMemberAnnotations(OpenApiSchema schema, MemberInfo memberInfo) + private static void ApplyMemberAnnotations(OpenApiSchema schema, MemberInfo memberInfo) { var schemaAttribute = memberInfo.GetCustomAttributes() .FirstOrDefault(); if (schemaAttribute != null) + { ApplySchemaAttribute(schema, schemaAttribute); + } } - private void ApplySchemaAttribute(OpenApiSchema schema, SwaggerSchemaAttribute schemaAttribute) + private static void ApplySchemaAttribute(OpenApiSchema schema, SwaggerSchemaAttribute schemaAttribute) { if (schemaAttribute.Description != null) + { schema.Description = schemaAttribute.Description; + } if (schemaAttribute.Format != null) + { schema.Format = schemaAttribute.Format; + } if (schemaAttribute.ReadOnlyFlag.HasValue) + { schema.ReadOnly = schemaAttribute.ReadOnlyFlag.Value; + } if (schemaAttribute.WriteOnlyFlag.HasValue) + { schema.WriteOnly = schemaAttribute.WriteOnlyFlag.Value; + } if (schemaAttribute.NullableFlag.HasValue) + { schema.Nullable = schemaAttribute.NullableFlag.Value; + } if (schemaAttribute.Required != null) + { schema.Required = new SortedSet(schemaAttribute.Required); + } if (schemaAttribute.Title != null) + { schema.Title = schemaAttribute.Title; + } } } diff --git a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsSwaggerGenOptionsExtensions.cs b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsSwaggerGenOptionsExtensions.cs index ad2f222a2f..04fe995eda 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/AnnotationsSwaggerGenOptionsExtensions.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/AnnotationsSwaggerGenOptionsExtensions.cs @@ -84,7 +84,7 @@ private static IEnumerable AnnotationsSubTypesSelector(Type type) } #endif - return Enumerable.Empty(); + return []; } private static string AnnotationsDiscriminatorNameSelector(Type baseType) diff --git a/src/Swashbuckle.AspNetCore.Annotations/SwaggerDiscriminatorAttribute.cs b/src/Swashbuckle.AspNetCore.Annotations/SwaggerDiscriminatorAttribute.cs index b342e19310..e6b107a109 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/SwaggerDiscriminatorAttribute.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/SwaggerDiscriminatorAttribute.cs @@ -1,12 +1,7 @@ namespace Swashbuckle.AspNetCore.Annotations; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, AllowMultiple = false)] -public class SwaggerDiscriminatorAttribute : Attribute +public class SwaggerDiscriminatorAttribute(string propertyName) : Attribute { - public SwaggerDiscriminatorAttribute(string propertyName) - { - PropertyName = propertyName; - } - - public string PropertyName { get; set; } + public string PropertyName { get; set; } = propertyName; } diff --git a/src/Swashbuckle.AspNetCore.Annotations/SwaggerOperationAttribute.cs b/src/Swashbuckle.AspNetCore.Annotations/SwaggerOperationAttribute.cs index 167931367f..fdac44996a 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/SwaggerOperationAttribute.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/SwaggerOperationAttribute.cs @@ -4,24 +4,18 @@ /// Enriches Operation metadata for a given action method /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] -public class SwaggerOperationAttribute : Attribute +public class SwaggerOperationAttribute(string summary = null, string description = null) : Attribute { - public SwaggerOperationAttribute(string summary = null, string description = null) - { - Summary = summary; - Description = description; - } - /// /// A short summary of what the operation does. For maximum readability in the swagger-ui, /// this field SHOULD be less than 120 characters. /// - public string Summary { get; set; } + public string Summary { get; set; } = summary; /// /// A verbose explanation of the operation behavior. GFM syntax can be used for rich text representation. /// - public string Description { get; set; } + public string Description { get; set; } = description; /// /// Unique string used to identify the operation. The id MUST be unique among all operations described diff --git a/src/Swashbuckle.AspNetCore.Annotations/SwaggerOperationFilterAttribute.cs b/src/Swashbuckle.AspNetCore.Annotations/SwaggerOperationFilterAttribute.cs index ebdab9374d..e949c841b9 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/SwaggerOperationFilterAttribute.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/SwaggerOperationFilterAttribute.cs @@ -1,12 +1,7 @@ namespace Swashbuckle.AspNetCore.Annotations; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] -public class SwaggerOperationFilterAttribute : Attribute +public class SwaggerOperationFilterAttribute(Type filterType) : Attribute { - public SwaggerOperationFilterAttribute(Type filterType) - { - FilterType = filterType; - } - - public Type FilterType { get; private set; } + public Type FilterType { get; private set; } = filterType; } diff --git a/src/Swashbuckle.AspNetCore.Annotations/SwaggerParameterAttribute.cs b/src/Swashbuckle.AspNetCore.Annotations/SwaggerParameterAttribute.cs index b491a89c84..3ccc6871f7 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/SwaggerParameterAttribute.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/SwaggerParameterAttribute.cs @@ -4,18 +4,13 @@ /// Enriches Parameter metadata for "path", "query" or "header" bound parameters or properties /// [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false)] -public class SwaggerParameterAttribute : Attribute +public class SwaggerParameterAttribute(string description = null) : Attribute { - public SwaggerParameterAttribute(string description = null) - { - Description = description; - } - /// /// A brief description of the parameter. This could contain examples of use. /// GFM syntax can be used for rich text representation /// - public string Description { get; set; } + public string Description { get; set; } = description; /// /// Determines whether the parameter is mandatory. If the parameter is in "path", diff --git a/src/Swashbuckle.AspNetCore.Annotations/SwaggerRequestBodyAttribute.cs b/src/Swashbuckle.AspNetCore.Annotations/SwaggerRequestBodyAttribute.cs index e221f7873c..977f5541b6 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/SwaggerRequestBodyAttribute.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/SwaggerRequestBodyAttribute.cs @@ -4,18 +4,14 @@ /// Enriches RequestBody metadata for "body" bound parameters or properties /// [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false)] -public class SwaggerRequestBodyAttribute : Attribute +public class SwaggerRequestBodyAttribute(string description = null) : Attribute { - public SwaggerRequestBodyAttribute(string description = null) - { - Description = description; - } /// /// A brief description of the requestBody. This could contain examples of use. /// GFM syntax can be used for rich text representation /// - public string Description { get; set; } + public string Description { get; set; } = description; /// /// Determines whether the requestBody is mandatory. If the parameter is in "path", diff --git a/src/Swashbuckle.AspNetCore.Annotations/SwaggerSchemaAttribute.cs b/src/Swashbuckle.AspNetCore.Annotations/SwaggerSchemaAttribute.cs index 3ca1ec53f2..478a6b18bc 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/SwaggerSchemaAttribute.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/SwaggerSchemaAttribute.cs @@ -7,14 +7,9 @@ AttributeTargets.Property | AttributeTargets.Enum, AllowMultiple = false)] -public class SwaggerSchemaAttribute : Attribute +public class SwaggerSchemaAttribute(string description = null) : Attribute { - public SwaggerSchemaAttribute(string description = null) - { - Description = description; - } - - public string Description { get; set; } + public string Description { get; set; } = description; public string Format { get; set; } diff --git a/src/Swashbuckle.AspNetCore.Annotations/SwaggerSchemaFilterAttribute.cs b/src/Swashbuckle.AspNetCore.Annotations/SwaggerSchemaFilterAttribute.cs index de31dd25d6..89581ac118 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/SwaggerSchemaFilterAttribute.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/SwaggerSchemaFilterAttribute.cs @@ -5,15 +5,9 @@ AttributeTargets.Struct | AttributeTargets.Enum, AllowMultiple = false)] -public class SwaggerSchemaFilterAttribute : Attribute +public class SwaggerSchemaFilterAttribute(Type type) : Attribute { - public SwaggerSchemaFilterAttribute(Type type) - { - Type = type; - Arguments = new object[]{ }; - } + public Type Type { get; private set; } = type; - public Type Type { get; private set; } - - public object[] Arguments { get; set; } + public object[] Arguments { get; set; } = []; } diff --git a/src/Swashbuckle.AspNetCore.Annotations/SwaggerSubTypeAttribute.cs b/src/Swashbuckle.AspNetCore.Annotations/SwaggerSubTypeAttribute.cs index b5689344d2..1819588527 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/SwaggerSubTypeAttribute.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/SwaggerSubTypeAttribute.cs @@ -1,14 +1,9 @@ namespace Swashbuckle.AspNetCore.Annotations; [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, AllowMultiple = true)] -public class SwaggerSubTypeAttribute : Attribute +public class SwaggerSubTypeAttribute(Type subType) : Attribute { - public SwaggerSubTypeAttribute(Type subType) - { - SubType = subType; - } - - public Type SubType { get; set; } + public Type SubType { get; set; } = subType; public string DiscriminatorValue { get; set; } } diff --git a/src/Swashbuckle.AspNetCore.Annotations/SwaggerSubTypesAttribute.cs b/src/Swashbuckle.AspNetCore.Annotations/SwaggerSubTypesAttribute.cs index e45eae32e4..aa2ab979fd 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/SwaggerSubTypesAttribute.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/SwaggerSubTypesAttribute.cs @@ -2,14 +2,9 @@ [Obsolete("Use multiple SwaggerSubType attributes instead")] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, AllowMultiple = false)] -public class SwaggerSubTypesAttribute : Attribute +public class SwaggerSubTypesAttribute(params Type[] subTypes) : Attribute { - public SwaggerSubTypesAttribute(params Type[] subTypes) - { - SubTypes = subTypes; - } - - public IEnumerable SubTypes { get; } + public IEnumerable SubTypes { get; } = subTypes; public string Discriminator { get; set; } } diff --git a/src/Swashbuckle.AspNetCore.Annotations/SwaggerTagAttribute.cs b/src/Swashbuckle.AspNetCore.Annotations/SwaggerTagAttribute.cs index 02bf644706..707d68c218 100644 --- a/src/Swashbuckle.AspNetCore.Annotations/SwaggerTagAttribute.cs +++ b/src/Swashbuckle.AspNetCore.Annotations/SwaggerTagAttribute.cs @@ -8,21 +8,16 @@ /// e.g. if you're customizing the tagging behavior with TagActionsBy. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] -public class SwaggerTagAttribute : Attribute +public class SwaggerTagAttribute(string description = null, string externalDocsUrl = null) : Attribute { - public SwaggerTagAttribute(string description = null, string externalDocsUrl = null) - { - Description = description; - ExternalDocsUrl = externalDocsUrl; - } /// /// A short description for the tag. GFM syntax can be used for rich text representation. /// - public string Description { get; } + public string Description { get; } = description; /// /// A URL for additional external documentation. Value MUST be in the format of a URL. /// - public string ExternalDocsUrl { get; } + public string ExternalDocsUrl { get; } = externalDocsUrl; } diff --git a/src/Swashbuckle.AspNetCore.ApiTesting.Xunit/ApiTestFixture.cs b/src/Swashbuckle.AspNetCore.ApiTesting.Xunit/ApiTestFixture.cs index 1d1bbc7012..b3f31dc22b 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting.Xunit/ApiTestFixture.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting.Xunit/ApiTestFixture.cs @@ -5,22 +5,15 @@ namespace Swashbuckle.AspNetCore.ApiTesting.Xunit; [Collection("ApiTests")] -public class ApiTestFixture : +public class ApiTestFixture( + ApiTestRunnerBase apiTestRunner, + WebApplicationFactory webAppFactory, + string documentName) : IClassFixture> where TEntryPoint : class { - private readonly ApiTestRunnerBase _apiTestRunner; - private readonly WebApplicationFactory _webAppFactory; - private readonly string _documentName; - - public ApiTestFixture( - ApiTestRunnerBase apiTestRunner, - WebApplicationFactory webAppFactory, - string documentName) - { - _apiTestRunner = apiTestRunner; - _webAppFactory = webAppFactory; - _documentName = documentName; - } + private readonly ApiTestRunnerBase _apiTestRunner = apiTestRunner; + private readonly WebApplicationFactory _webAppFactory = webAppFactory; + private readonly string _documentName = documentName; public void Describe(string pathTemplate, OperationType operationType, OpenApiOperation operationSpec) { diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/ApiTestRunnerBase.cs b/src/Swashbuckle.AspNetCore.ApiTesting/ApiTestRunnerBase.cs index 7b409bf21e..7c62c37860 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/ApiTestRunnerBase.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/ApiTestRunnerBase.cs @@ -29,8 +29,7 @@ public void ConfigureOperation( { var openApiDocument = _options.GetOpenApiDocument(documentName); - if (openApiDocument.Paths == null) - openApiDocument.Paths = new OpenApiPaths(); + openApiDocument.Paths ??= []; if (!openApiDocument.Paths.TryGetValue(pathTemplate, out OpenApiPathItem pathItem)) { @@ -49,11 +48,20 @@ public async Task TestAsync( HttpClient httpClient) { var openApiDocument = _options.GetOpenApiDocument(documentName); + if (!openApiDocument.TryFindOperationById(operationId, out string pathTemplate, out OperationType operationType)) + { throw new InvalidOperationException($"Operation with id '{operationId}' not found in OpenAPI document '{documentName}'"); + } +#if NET + if (expectedStatusCode.StartsWith('2')) +#else if (expectedStatusCode.StartsWith("2")) +#endif + { _requestValidator.Validate(request, openApiDocument, pathTemplate, operationType); + } var response = await httpClient.SendAsync(request); @@ -62,22 +70,27 @@ public async Task TestAsync( public void Dispose() { - if (!_options.GenerateOpenApiFiles) return; + if (!_options.GenerateOpenApiFiles) + { + return; + } if (_options.FileOutputRoot == null) + { throw new Exception("GenerateOpenApiFiles set but FileOutputRoot is null"); + } foreach (var entry in _options.OpenApiDocs) { var outputDir = Path.Combine(_options.FileOutputRoot, entry.Key); Directory.CreateDirectory(outputDir); - using (var streamWriter = new StreamWriter(Path.Combine(outputDir, "openapi.json"))) - { - var openApiJsonWriter = new OpenApiJsonWriter(streamWriter); - entry.Value.SerializeAsV3(openApiJsonWriter); - streamWriter.Close(); - } + using var streamWriter = new StreamWriter(Path.Combine(outputDir, "openapi.json")); + + var openApiJsonWriter = new OpenApiJsonWriter(streamWriter); + entry.Value.SerializeAsV3(openApiJsonWriter); } + + GC.SuppressFinalize(this); } } diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/ApiTestRunnerOptions.cs b/src/Swashbuckle.AspNetCore.ApiTesting/ApiTestRunnerOptions.cs index e3bd6a0755..0aaad418f2 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/ApiTestRunnerOptions.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/ApiTestRunnerOptions.cs @@ -6,8 +6,8 @@ public class ApiTestRunnerOptions { public ApiTestRunnerOptions() { - OpenApiDocs = new Dictionary(); - ContentValidators = new List { new JsonContentValidator() }; + OpenApiDocs = []; + ContentValidators = [new JsonContentValidator()]; GenerateOpenApiFiles = false; FileOutputRoot = null; } diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonAllOfValidator.cs b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonAllOfValidator.cs index 397253266b..c9751582bd 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonAllOfValidator.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonAllOfValidator.cs @@ -15,19 +15,20 @@ public bool Validate( JToken instance, out IEnumerable errorMessages) { - var errorMessagesList = new List(); + var errors = new List(); - var allOfArray = schema.AllOf.ToArray(); - - for (int i = 0; i < allOfArray.Length; i++) + if (schema.AllOf is { } allOf) { - if (!_jsonValidator.Validate(allOfArray[i], openApiDocument, instance, out IEnumerable subErrorMessages)) + for (int i = 0; i < allOf.Count; i++) { - errorMessagesList.AddRange(subErrorMessages.Select(msg => $"{msg} (allOf[{i}])")); + if (!_jsonValidator.Validate(allOf[i], openApiDocument, instance, out IEnumerable subErrorMessages)) + { + errors.AddRange(subErrorMessages.Select(msg => $"{msg} (allOf[{i}])")); + } } } - errorMessages = errorMessagesList; + errorMessages = errors; return !errorMessages.Any(); } } diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonAnyOfValidator.cs b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonAnyOfValidator.cs index ceb7f239a9..50e0d602b5 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonAnyOfValidator.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonAnyOfValidator.cs @@ -15,22 +15,23 @@ public bool Validate( JToken instance, out IEnumerable errorMessages) { - var errorMessagesList = new List(); + var errors = new List(); - var anyOfArray = schema.AnyOf.ToArray(); - - for (int i = 0; i < anyOfArray.Length; i++) + if (schema.AnyOf is { } anyOf) { - if (_jsonValidator.Validate(anyOfArray[i], openApiDocument, instance, out IEnumerable subErrorMessages)) + for (int i = 0; i < anyOf.Count; i++) { - errorMessages = []; - return true; - } + if (_jsonValidator.Validate(anyOf[i], openApiDocument, instance, out IEnumerable subErrorMessages)) + { + errorMessages = []; + return true; + } - errorMessagesList.AddRange(subErrorMessages.Select(msg => $"{msg} (anyOf[{i}])")); + errors.AddRange(subErrorMessages.Select(msg => $"{msg} (anyOf[{i}])")); + } } - errorMessages = errorMessagesList; + errorMessages = errors; return !errorMessages.Any(); } } diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonArrayValidator.cs b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonArrayValidator.cs index db681f9961..c6d297014f 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonArrayValidator.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonArrayValidator.cs @@ -22,7 +22,7 @@ public bool Validate( } var arrayInstance = (JArray)instance; - var errorMessagesList = new List(); + var errors = new List(); // items if (schema.Items != null) @@ -31,7 +31,7 @@ public bool Validate( { if (!_jsonValidator.Validate(schema.Items, openApiDocument, itemInstance, out IEnumerable itemErrorMessages)) { - errorMessagesList.AddRange(itemErrorMessages); + errors.AddRange(itemErrorMessages); } } } @@ -39,22 +39,22 @@ public bool Validate( // maxItems if (schema.MaxItems.HasValue && (arrayInstance.Count > schema.MaxItems.Value)) { - errorMessagesList.Add($"Path: {instance.Path}. Array size is greater than maxItems"); + errors.Add($"Path: {instance.Path}. Array size is greater than maxItems"); } // minItems if (schema.MinItems.HasValue && (arrayInstance.Count < schema.MinItems.Value)) { - errorMessagesList.Add($"Path: {instance.Path}. Array size is less than minItems"); + errors.Add($"Path: {instance.Path}. Array size is less than minItems"); } // uniqueItems if (schema.UniqueItems.HasValue && (arrayInstance.Count != arrayInstance.Distinct().Count())) { - errorMessagesList.Add($"Path: {instance.Path}. Array does not contain uniqueItems"); + errors.Add($"Path: {instance.Path}. Array does not contain uniqueItems"); } - errorMessages = errorMessagesList; + errorMessages = errors; return !errorMessages.Any(); } } diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonNumberValidator.cs b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonNumberValidator.cs index 9101fb5010..99cafbd2a2 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonNumberValidator.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonNumberValidator.cs @@ -20,12 +20,12 @@ public bool Validate( } var numberValue = instance.Value(); - var errorMessagesList = new List(); + var errors = new List(); // multipleOf if (schema.MultipleOf.HasValue && ((numberValue % schema.MultipleOf.Value) != 0)) { - errorMessagesList.Add($"Path: {instance.Path}. Number is not evenly divisible by multipleOf"); + errors.Add($"Path: {instance.Path}. Number is not evenly divisible by multipleOf"); } // maximum & exclusiveMaximum @@ -35,11 +35,11 @@ public bool Validate( if (exclusiveMaximum && (numberValue >= schema.Maximum.Value)) { - errorMessagesList.Add($"Path: {instance.Path}. Number is greater than, or equal to, maximum"); + errors.Add($"Path: {instance.Path}. Number is greater than, or equal to, maximum"); } else if (numberValue > schema.Maximum.Value) { - errorMessagesList.Add($"Path: {instance.Path}. Number is greater than maximum"); + errors.Add($"Path: {instance.Path}. Number is greater than maximum"); } } @@ -50,15 +50,15 @@ public bool Validate( if (exclusiveMinimum && (numberValue <= schema.Minimum.Value)) { - errorMessagesList.Add($"Path: {instance.Path}. Number is less than, or equal to, minimum"); + errors.Add($"Path: {instance.Path}. Number is less than, or equal to, minimum"); } else if (numberValue < schema.Minimum.Value) { - errorMessagesList.Add($"Path: {instance.Path}. Number is less than minimum"); + errors.Add($"Path: {instance.Path}. Number is less than minimum"); } } - errorMessages = errorMessagesList; + errorMessages = errors; return !errorMessages.Any(); } } diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonObjectValidator.cs b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonObjectValidator.cs index 3b357609c8..cd93f39133 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonObjectValidator.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonObjectValidator.cs @@ -23,24 +23,24 @@ public bool Validate( var jObject = (JObject)instance; var properties = jObject.Properties(); - var errorMessagesList = new List(); + var errors = new List(); // maxProperties if (schema.MaxProperties.HasValue && properties.Count() > schema.MaxProperties.Value) { - errorMessagesList.Add($"Path: {instance.Path}. Number of properties is greater than maxProperties"); + errors.Add($"Path: {instance.Path}. Number of properties is greater than maxProperties"); } // minProperties if (schema.MinProperties.HasValue && properties.Count() < schema.MinProperties.Value) { - errorMessagesList.Add($"Path: {instance.Path}. Number of properties is less than minProperties"); + errors.Add($"Path: {instance.Path}. Number of properties is less than minProperties"); } // required if (schema.Required != null && schema.Required.Except(properties.Select(p => p.Name)).Any()) { - errorMessagesList.Add($"Path: {instance.Path}. Required property(s) not present"); + errors.Add($"Path: {instance.Path}. Required property(s) not present"); } foreach (var property in properties) @@ -52,7 +52,7 @@ public bool Validate( { if (!_jsonValidator.Validate(propertySchema, openApiDocument, property.Value, out propertyErrorMessages)) { - errorMessagesList.AddRange(propertyErrorMessages); + errors.AddRange(propertyErrorMessages); } continue; @@ -60,18 +60,18 @@ public bool Validate( if (!schema.AdditionalPropertiesAllowed) { - errorMessagesList.Add($"Path: {instance.Path}. Additional properties not allowed"); + errors.Add($"Path: {instance.Path}. Additional properties not allowed"); } // additionalProperties if (schema.AdditionalProperties != null && !_jsonValidator.Validate(schema.AdditionalProperties, openApiDocument, property.Value, out propertyErrorMessages)) { - errorMessagesList.AddRange(propertyErrorMessages); + errors.AddRange(propertyErrorMessages); } } - errorMessages = errorMessagesList; + errorMessages = errors; return !errorMessages.Any(); } } diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonOneOfValidator.cs b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonOneOfValidator.cs index 3b1ae31fe5..cb3b449e9a 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonOneOfValidator.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonOneOfValidator.cs @@ -15,26 +15,27 @@ public bool Validate( JToken instance, out IEnumerable errorMessages) { - var errorMessagesList = new List(); - - var oneOfArray = schema.OneOf.ToArray(); - + var errors = new List(); int matched = 0; - for (int i = 0; i < oneOfArray.Length; i++) + + if (schema.OneOf is { } oneOf) { - if (_jsonValidator.Validate(oneOfArray[i], openApiDocument, instance, out IEnumerable subErrorMessages)) - { - matched++; - } - else + for (int i = 0; i < oneOf.Count; i++) { - errorMessagesList.AddRange(subErrorMessages.Select(msg => $"{msg} (oneOf[{i}])")); + if (_jsonValidator.Validate(oneOf[i], openApiDocument, instance, out IEnumerable subErrorMessages)) + { + matched++; + } + else + { + errors.AddRange(subErrorMessages.Select(msg => $"{msg} (oneOf[{i}])")); + } } } if (matched == 0) { - errorMessages = errorMessagesList; + errorMessages = errors; return false; } diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonStringValidator.cs b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonStringValidator.cs index f044526ac5..48bb18bdca 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonStringValidator.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonStringValidator.cs @@ -21,27 +21,27 @@ public bool Validate( } var stringValue = instance.Value(); - var errorMessagesList = new List(); + var errors = new List(); // maxLength if (schema.MaxLength.HasValue && (stringValue.Length > schema.MaxLength.Value)) { - errorMessagesList.Add($"Path: {instance.Path}. String length is greater than maxLength"); + errors.Add($"Path: {instance.Path}. String length is greater than maxLength"); } // minLength if (schema.MinLength.HasValue && (stringValue.Length < schema.MinLength.Value)) { - errorMessagesList.Add($"Path: {instance.Path}. String length is less than minLength"); + errors.Add($"Path: {instance.Path}. String length is less than minLength"); } // pattern if ((schema.Pattern != null) && !Regex.IsMatch(stringValue, schema.Pattern)) { - errorMessagesList.Add($"Path: {instance.Path}. String does not match pattern"); + errors.Add($"Path: {instance.Path}. String does not match pattern"); } - errorMessages = errorMessagesList; + errorMessages = errors; return !errorMessages.Any(); } } diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonValidator.cs b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonValidator.cs index 1fc1ee02dd..553a77c9d4 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonValidator.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/JsonValidation/JsonValidator.cs @@ -35,7 +35,7 @@ public bool Validate( ? (OpenApiSchema)openApiDocument.ResolveReference(schema.Reference) : schema; - var errorMessagesList = new List(); + var errors = new List(); foreach (var subValidator in _subValidators) { @@ -46,11 +46,11 @@ public bool Validate( if (!subValidator.Validate(schema, openApiDocument, instance, out IEnumerable subErrorMessages)) { - errorMessagesList.AddRange(subErrorMessages); + errors.AddRange(subErrorMessages); } } - errorMessages = errorMessagesList; + errorMessages = errors; return !errorMessages.Any(); } } diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/OpenApiDocumentExtensions.cs b/src/Swashbuckle.AspNetCore.ApiTesting/OpenApiDocumentExtensions.cs index 914cc93d1e..caaa119b35 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/OpenApiDocumentExtensions.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/OpenApiDocumentExtensions.cs @@ -10,7 +10,7 @@ internal static bool TryFindOperationById( out string pathTemplate, out OperationType operationType) { - foreach (var pathEntry in openApiDocument.Paths ?? new OpenApiPaths()) + foreach (var pathEntry in openApiDocument.Paths ?? []) { var pathItem = pathEntry.Value; @@ -26,7 +26,7 @@ internal static bool TryFindOperationById( } pathTemplate = null; - operationType = default(OperationType); + operationType = default; return false; } @@ -39,7 +39,9 @@ internal static OpenApiOperation GetOperationByPathAndType( if (openApiDocument.Paths.TryGetValue(pathTemplate, out pathSpec)) { if (pathSpec.Operations.TryGetValue(operationType, out var type)) + { return type; + } } throw new InvalidOperationException($"Operation with path '{pathTemplate}' and type '{operationType}' not found"); diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/RequestDoesNotMatchSpecException.cs b/src/Swashbuckle.AspNetCore.ApiTesting/RequestDoesNotMatchSpecException.cs new file mode 100644 index 0000000000..58ece56d04 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.ApiTesting/RequestDoesNotMatchSpecException.cs @@ -0,0 +1,3 @@ +namespace Swashbuckle.AspNetCore.ApiTesting; + +public class RequestDoesNotMatchSpecException(string message) : Exception(message); diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/RequestValidator.cs b/src/Swashbuckle.AspNetCore.ApiTesting/RequestValidator.cs index 793abf6cf4..2185d5492c 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/RequestValidator.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/RequestValidator.cs @@ -108,7 +108,7 @@ private static void ValidateParameters( continue; } - var schema = (parameterSpec.Schema.Reference != null) ? + var schema = parameterSpec.Schema.Reference != null ? (OpenApiSchema)openApiDocument.ResolveReference(parameterSpec.Schema.Reference) : parameterSpec.Schema; @@ -156,5 +156,3 @@ private void ValidateContent(OpenApiRequestBody requestBodySpec, OpenApiDocument } } } - -public class RequestDoesNotMatchSpecException(string message) : Exception(message); diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/ResponseDoesNotMatchSpecException.cs b/src/Swashbuckle.AspNetCore.ApiTesting/ResponseDoesNotMatchSpecException.cs new file mode 100644 index 0000000000..d9ce76e440 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.ApiTesting/ResponseDoesNotMatchSpecException.cs @@ -0,0 +1,3 @@ +namespace Swashbuckle.AspNetCore.ApiTesting; + +public class ResponseDoesNotMatchSpecException(string message) : Exception(message); diff --git a/src/Swashbuckle.AspNetCore.ApiTesting/ResponseValidator.cs b/src/Swashbuckle.AspNetCore.ApiTesting/ResponseValidator.cs index ec7f58c793..f54bd05748 100644 --- a/src/Swashbuckle.AspNetCore.ApiTesting/ResponseValidator.cs +++ b/src/Swashbuckle.AspNetCore.ApiTesting/ResponseValidator.cs @@ -54,7 +54,7 @@ private static void ValidateHeaders( continue; } - var schema = (headerSpec.Schema.Reference != null) ? + var schema = headerSpec.Schema.Reference != null ? (OpenApiSchema)openApiDocument.ResolveReference(headerSpec.Schema.Reference) : headerSpec.Schema; @@ -101,5 +101,3 @@ private void ValidateContent( } } } - -public class ResponseDoesNotMatchSpecException(string message) : Exception(message); diff --git a/src/Swashbuckle.AspNetCore.Cli/CommandRunner.cs b/src/Swashbuckle.AspNetCore.Cli/CommandRunner.cs index c106119644..0e874c0178 100644 --- a/src/Swashbuckle.AspNetCore.Cli/CommandRunner.cs +++ b/src/Swashbuckle.AspNetCore.Cli/CommandRunner.cs @@ -1,27 +1,16 @@ namespace Swashbuckle.AspNetCore.Cli; -internal class CommandRunner +internal class CommandRunner(string commandName, string commandDescription, TextWriter output) { - private readonly Dictionary _argumentDescriptors; - private readonly Dictionary _optionDescriptors; - private Func, int> _runFunc; - private readonly List _subRunners; - private readonly TextWriter _output; + private readonly Dictionary _argumentDescriptors = []; + private readonly Dictionary _optionDescriptors = []; + private Func, int> _runFunc = (_) => 1; + private readonly List _subRunners = []; + private readonly TextWriter _output = output; - public CommandRunner(string commandName, string commandDescription, TextWriter output) - { - CommandName = commandName; - CommandDescription = commandDescription; - _argumentDescriptors = []; - _optionDescriptors = []; - _runFunc = (_) => 1; // no-op - _subRunners = []; - _output = output; - } + public string CommandName { get; private set; } = commandName; - public string CommandName { get; private set; } - - public string CommandDescription { get; private set; } + public string CommandDescription { get; private set; } = commandDescription; public void Argument(string name, string description) { @@ -30,7 +19,11 @@ public void Argument(string name, string description) public void Option(string name, string description, bool isFlag = false) { - if (!name.StartsWith("--")) throw new ArgumentException("name of option must begin with --"); + if (!name.StartsWith("--")) + { + throw new ArgumentException("name of option must begin with --"); + } + _optionDescriptors.Add(name, new OptionDescriptor { Description = description, IsFlag = isFlag }); } @@ -54,7 +47,7 @@ public int Run(IEnumerable args) if (subRunner != null) return subRunner.Run(args.Skip(1)); } - if (_subRunners.Any() || !TryParseArgs(args, out IDictionary namedArgs)) + if (_subRunners.Count != 0 || !TryParseArgs(args, out IDictionary namedArgs)) { PrintUsage(); return 1; @@ -69,16 +62,20 @@ private bool TryParseArgs(IEnumerable args, out IDictionary(args); // Process options first - while (argsQueue.Any() && argsQueue.Peek().StartsWith("--")) + while (argsQueue.Count != 0 && argsQueue.Peek().StartsWith("--")) { // Ensure it's a known option var name = argsQueue.Dequeue(); if (!_optionDescriptors.TryGetValue(name, out OptionDescriptor optionDescriptor)) + { return false; + } // If it's not a flag, ensure it's followed by a corresponding value - if (!optionDescriptor.IsFlag && (!argsQueue.Any() || argsQueue.Peek().StartsWith("--"))) + if (!optionDescriptor.IsFlag && (argsQueue.Count == 0 || argsQueue.Peek().StartsWith("--"))) + { return false; + } namedArgs.Add(name, (!optionDescriptor.IsFlag ? argsQueue.Dequeue() : null)); } @@ -86,16 +83,19 @@ private bool TryParseArgs(IEnumerable args, out IDictionary $"[{name}]"); _output.WriteLine($"Usage: {CommandName} {optionsPart}{string.Join(" ", argParts)}"); _output.WriteLine(); @@ -125,7 +129,7 @@ private void PrintUsage() } // Options - if (_optionDescriptors.Any()) + if (_optionDescriptors.Count != 0) { _output.WriteLine("options:"); foreach (var entry in _optionDescriptors) diff --git a/src/Swashbuckle.AspNetCore.Cli/HostFactoryResolver.cs b/src/Swashbuckle.AspNetCore.Cli/HostFactoryResolver.cs index f20841dcb1..5559d608d7 100644 --- a/src/Swashbuckle.AspNetCore.Cli/HostFactoryResolver.cs +++ b/src/Swashbuckle.AspNetCore.Cli/HostFactoryResolver.cs @@ -38,11 +38,12 @@ public static Func ResolveHostBuilderFactory ResolveHostFactory(Assembly assembly, - TimeSpan? waitTimeout = null, - bool stopApplication = true, - Action configureHostBuilder = null, - Action entrypointCompleted = null) + public static Func ResolveHostFactory( + Assembly assembly, + TimeSpan? waitTimeout = null, + bool stopApplication = true, + Action configureHostBuilder = null, + Action entrypointCompleted = null) { if (assembly.EntryPoint is null) { @@ -163,35 +164,25 @@ private static IServiceProvider GetServiceProvider(object host) return (IServiceProvider)servicesProperty?.GetValue(host); } - private sealed class HostingListener : IObserver, IObserver> + private sealed class HostingListener( + string[] args, + MethodInfo entryPoint, + TimeSpan waitTimeout, + bool stopApplication, + Action configure, + Action entrypointCompleted) : IObserver, IObserver> { - private readonly string[] _args; - private readonly MethodInfo _entryPoint; - private readonly TimeSpan _waitTimeout; - private readonly bool _stopApplication; + private readonly string[] _args = args; + private readonly MethodInfo _entryPoint = entryPoint; + private readonly TimeSpan _waitTimeout = waitTimeout; + private readonly bool _stopApplication = stopApplication; private readonly TaskCompletionSource _hostTcs = new(); private IDisposable _disposable; - private readonly Action _configure; - private readonly Action _entrypointCompleted; + private readonly Action _configure = configure; + private readonly Action _entrypointCompleted = entrypointCompleted; private static readonly AsyncLocal _currentListener = new(); - public HostingListener( - string[] args, - MethodInfo entryPoint, - TimeSpan waitTimeout, - bool stopApplication, - Action configure, - Action entrypointCompleted) - { - _args = args; - _entryPoint = entryPoint; - _waitTimeout = waitTimeout; - _stopApplication = stopApplication; - _configure = configure; - _entrypointCompleted = entrypointCompleted; - } - public object CreateHost() { using var subscription = DiagnosticListener.AllListeners.Subscribe(this); diff --git a/src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/JsonPropertyExtensions.cs b/src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/JsonPropertyExtensions.cs index c849265d9d..9b7a895b59 100644 --- a/src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/JsonPropertyExtensions.cs +++ b/src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/JsonPropertyExtensions.cs @@ -8,14 +8,13 @@ public static class JsonPropertyExtensions { public static bool TryGetMemberInfo(this JsonProperty jsonProperty, out MemberInfo memberInfo) { - memberInfo = jsonProperty.DeclaringType?.GetMember(jsonProperty.UnderlyingName) + memberInfo = jsonProperty.DeclaringType? + .GetMember(jsonProperty.UnderlyingName) .FirstOrDefault(); - return (memberInfo != null); + return memberInfo != null; } public static bool IsRequiredSpecified(this JsonProperty jsonProperty) - { - return jsonProperty.Required != Required.Default; - } + => jsonProperty.Required != Required.Default; } diff --git a/src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/NewtonsoftDataContractResolver.cs b/src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/NewtonsoftDataContractResolver.cs index 26617ae6fd..88e62d5913 100644 --- a/src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/NewtonsoftDataContractResolver.cs +++ b/src/Swashbuckle.AspNetCore.Newtonsoft/SchemaGenerator/NewtonsoftDataContractResolver.cs @@ -7,16 +7,10 @@ namespace Swashbuckle.AspNetCore.Newtonsoft; -public class NewtonsoftDataContractResolver : ISerializerDataContractResolver +public class NewtonsoftDataContractResolver(JsonSerializerSettings serializerSettings) : ISerializerDataContractResolver { - private readonly JsonSerializerSettings _serializerSettings; - private readonly IContractResolver _contractResolver; - - public NewtonsoftDataContractResolver(JsonSerializerSettings serializerSettings) - { - _serializerSettings = serializerSettings; - _contractResolver = serializerSettings.ContractResolver ?? new DefaultContractResolver(); - } + private readonly JsonSerializerSettings _serializerSettings = serializerSettings; + private readonly IContractResolver _contractResolver = serializerSettings.ContractResolver ?? new DefaultContractResolver(); public DataContract GetDataContractForType(Type type) { @@ -49,8 +43,12 @@ public DataContract GetDataContractForType(Type type) var enumValues = jsonContract.UnderlyingType.GetEnumValues(); // Test to determine if the serializer will treat as string - var serializeAsString = (enumValues.Length > 0) - && JsonConverterFunc(enumValues.GetValue(0)).StartsWith("\""); + var serializeAsString = (enumValues.Length > 0) && +#if NET + JsonConverterFunc(enumValues.GetValue(0)).StartsWith('\"'); +#else + JsonConverterFunc(enumValues.GetValue(0)).StartsWith("\""); +#endif var primitiveTypeAndFormat = serializeAsString ? PrimitiveTypesAndFormats[typeof(string)] @@ -85,7 +83,12 @@ public DataContract GetDataContractForType(Type type) .Cast() .Select(JsonConverterFunc); - keys = enumValuesAsJson.Any(json => json.StartsWith("\"")) + keys = +#if NET + enumValuesAsJson.Any(json => json.StartsWith('\"')) +#else + enumValuesAsJson.Any(json => json.StartsWith("\"")) +#endif ? enumValuesAsJson.Select(json => json.Replace("\"", string.Empty)) : keyType.GetEnumNames(); } @@ -112,9 +115,9 @@ public DataContract GetDataContractForType(Type type) string typeNameProperty = null; string typeNameValue = null; - if (_serializerSettings.TypeNameHandling == TypeNameHandling.Objects - || _serializerSettings.TypeNameHandling == TypeNameHandling.All - || _serializerSettings.TypeNameHandling == TypeNameHandling.Auto) + if (_serializerSettings.TypeNameHandling == TypeNameHandling.Objects || + _serializerSettings.TypeNameHandling == TypeNameHandling.All || + _serializerSettings.TypeNameHandling == TypeNameHandling.Auto) { typeNameProperty = "$type"; @@ -138,9 +141,7 @@ public DataContract GetDataContractForType(Type type) } private string JsonConverterFunc(object value) - { - return JsonConvert.SerializeObject(value, _serializerSettings); - } + => JsonConvert.SerializeObject(value, _serializerSettings); private List GetDataPropertiesFor(JsonObjectContract jsonObjectContract, out Type extensionDataType) { @@ -170,7 +171,7 @@ private List GetDataPropertiesFor(JsonObjectContract jsonObjectCon dataProperties.Add( new DataProperty( name: jsonProperty.PropertyName, - isRequired: (required == Required.Always || required == Required.AllowNull), + isRequired: required == Required.Always || required == Required.AllowNull, isNullable: (required == Required.AllowNull || required == Required.Default) && jsonProperty.PropertyType.IsReferenceOrNullableType(), isReadOnly: jsonProperty.Readable && !jsonProperty.Writable && !isSetViaConstructor, isWriteOnly: jsonProperty.Writable && !jsonProperty.Readable, diff --git a/src/Swashbuckle.AspNetCore.ReDoc/ConfigObject.cs b/src/Swashbuckle.AspNetCore.ReDoc/ConfigObject.cs new file mode 100644 index 0000000000..266a1b25fb --- /dev/null +++ b/src/Swashbuckle.AspNetCore.ReDoc/ConfigObject.cs @@ -0,0 +1,78 @@ +using System.Text.Json.Serialization; + +namespace Swashbuckle.AspNetCore.ReDoc; + +public class ConfigObject +{ + /// + /// If set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. + /// Disabled by default for performance reasons. Enable this option if you work with untrusted user data! + /// + public bool UntrustedSpec { get; set; } + + /// + /// If set, specifies a vertical scroll-offset in pixels. + /// This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc + /// + public int? ScrollYOffset { get; set; } + + /// + /// If set, the protocol and hostname is not shown in the operation definition + /// + public bool HideHostname { get; set; } + + /// + /// Do not show "Download" spec button. THIS DOESN'T MAKE YOUR SPEC PRIVATE, it just hides the button + /// + public bool HideDownloadButton { get; set; } + + /// + /// Specify which responses to expand by default by response codes. + /// Values should be passed as comma-separated list without spaces e.g. "200,201". Special value "all" expands all responses by default. + /// Be careful: this option can slow-down documentation rendering time. + /// + public string ExpandResponses { get; set; } = "all"; + + /// + /// Show required properties first ordered in the same order as in required array + /// + public bool RequiredPropsFirst { get; set; } + + /// + /// Do not inject Authentication section automatically + /// + public bool NoAutoAuth { get; set; } + + /// + /// Show path link and HTTP verb in the middle panel instead of the right one + /// + public bool PathInMiddlePanel { get; set; } + + /// + /// Do not show loading animation. Useful for small docs + /// + public bool HideLoading { get; set; } + + /// + /// Use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs) + /// + public bool NativeScrollbars { get; set; } + + /// + /// Disable search indexing and search box + /// + public bool DisableSearch { get; set; } + + /// + /// Show only required fields in request samples + /// + public bool OnlyRequiredInSamples { get; set; } + + /// + /// Sort properties alphabetically + /// + public bool SortPropsAlphabetically { get; set; } + + [JsonExtensionData] + public Dictionary AdditionalItems { get; set; } = []; +} diff --git a/src/Swashbuckle.AspNetCore.ReDoc/ReDocBuilderExtensions.cs b/src/Swashbuckle.AspNetCore.ReDoc/ReDocBuilderExtensions.cs index b169c0f2d5..cb89a7748d 100644 --- a/src/Swashbuckle.AspNetCore.ReDoc/ReDocBuilderExtensions.cs +++ b/src/Swashbuckle.AspNetCore.ReDoc/ReDocBuilderExtensions.cs @@ -29,10 +29,7 @@ public static IApplicationBuilder UseReDoc( } // To simplify the common case, use a default that will work with the SwaggerMiddleware defaults - if (options.SpecUrl == null) - { - options.SpecUrl = "../swagger/v1/swagger.json"; - } + options.SpecUrl ??= "../swagger/v1/swagger.json"; return app.UseReDoc(options); } diff --git a/src/Swashbuckle.AspNetCore.ReDoc/ReDocMiddleware.cs b/src/Swashbuckle.AspNetCore.ReDoc/ReDocMiddleware.cs index cca8e7a783..653e8100b6 100644 --- a/src/Swashbuckle.AspNetCore.ReDoc/ReDocMiddleware.cs +++ b/src/Swashbuckle.AspNetCore.ReDoc/ReDocMiddleware.cs @@ -1,18 +1,19 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; +using System.Reflection; using System.Text; using System.Text.Json; -using System.Text.Json.Serialization; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -#if !NET +#if NET +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Hosting; +#else +using System.Text.Json.Serialization; using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; #endif @@ -65,7 +66,12 @@ public async Task Invoke(HttpContext httpContext) if (Regex.IsMatch(path, $"^/?{Regex.Escape(_options.RoutePrefix)}/?$", RegexOptions.IgnoreCase)) { // Use relative redirect to support proxy environments - var relativeIndexUrl = string.IsNullOrEmpty(path) || path.EndsWith("/") + var relativeIndexUrl = +#if NET + string.IsNullOrEmpty(path) || path.EndsWith('/') +#else + string.IsNullOrEmpty(path) || path.EndsWith("/") +#endif ? "index.html" : $"{path.Split('/').Last()}/index.html"; diff --git a/src/Swashbuckle.AspNetCore.ReDoc/ReDocOptions.cs b/src/Swashbuckle.AspNetCore.ReDoc/ReDocOptions.cs index 0dcae7389c..370972bd0b 100644 --- a/src/Swashbuckle.AspNetCore.ReDoc/ReDocOptions.cs +++ b/src/Swashbuckle.AspNetCore.ReDoc/ReDocOptions.cs @@ -1,5 +1,4 @@ using System.Text.Json; -using System.Text.Json.Serialization; namespace Swashbuckle.AspNetCore.ReDoc; @@ -13,7 +12,7 @@ public class ReDocOptions /// /// Gets or sets a Stream function for retrieving the redoc page /// - public Func IndexStream { get; set; } = () => ResourceHelper.GetEmbeddedResource("index.html"); + public Func IndexStream { get; set; } = static () => ResourceHelper.GetEmbeddedResource("index.html"); /// /// Gets or sets a title for the redoc page @@ -28,90 +27,15 @@ public class ReDocOptions /// /// Gets or sets the Swagger JSON endpoint. Can be fully-qualified or relative to the redoc page /// - public string SpecUrl { get; set; } = null; + public string SpecUrl { get; set; } /// /// Gets or sets the to use. /// - public ConfigObject ConfigObject { get; set; } = new ConfigObject(); + public ConfigObject ConfigObject { get; set; } = new(); /// /// Gets or sets the optional to use. /// public JsonSerializerOptions JsonSerializerOptions { get; set; } } - -public class ConfigObject -{ - /// - /// If set, the spec is considered untrusted and all HTML/markdown is sanitized to prevent XSS. - /// Disabled by default for performance reasons. Enable this option if you work with untrusted user data! - /// - public bool UntrustedSpec { get; set; } = false; - - /// - /// If set, specifies a vertical scroll-offset in pixels. - /// This is often useful when there are fixed positioned elements at the top of the page, such as navbars, headers etc - /// - public int? ScrollYOffset { get; set; } - - /// - /// If set, the protocol and hostname is not shown in the operation definition - /// - public bool HideHostname { get; set; } = false; - - /// - /// Do not show "Download" spec button. THIS DOESN'T MAKE YOUR SPEC PRIVATE, it just hides the button - /// - public bool HideDownloadButton { get; set; } = false; - - /// - /// Specify which responses to expand by default by response codes. - /// Values should be passed as comma-separated list without spaces e.g. "200,201". Special value "all" expands all responses by default. - /// Be careful: this option can slow-down documentation rendering time. - /// - public string ExpandResponses { get; set; } = "all"; - - /// - /// Show required properties first ordered in the same order as in required array - /// - public bool RequiredPropsFirst { get; set; } = false; - - /// - /// Do not inject Authentication section automatically - /// - public bool NoAutoAuth { get; set; } = false; - - /// - /// Show path link and HTTP verb in the middle panel instead of the right one - /// - public bool PathInMiddlePanel { get; set; } = false; - - /// - /// Do not show loading animation. Useful for small docs - /// - public bool HideLoading { get; set; } = false; - - /// - /// Use native scrollbar for sidemenu instead of perfect-scroll (scrolling performance optimization for big specs) - /// - public bool NativeScrollbars { get; set; } = false; - - /// - /// Disable search indexing and search box - /// - public bool DisableSearch { get; set; } = false; - - /// - /// Show only required fields in request samples - /// - public bool OnlyRequiredInSamples { get; set; } = false; - - /// - /// Sort properties alphabetically - /// - public bool SortPropsAlphabetically { get; set; } = false; - - [JsonExtensionData] - public Dictionary AdditionalItems { get; set; } = new Dictionary(); -} diff --git a/src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/SwaggerBuilderExtensions.cs b/src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/SwaggerBuilderExtensions.cs index 54953b148c..3e542e390b 100644 --- a/src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/SwaggerBuilderExtensions.cs +++ b/src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/SwaggerBuilderExtensions.cs @@ -8,67 +8,66 @@ using Microsoft.Extensions.Options; using Swashbuckle.AspNetCore.Swagger; -namespace Microsoft.AspNetCore.Builder +namespace Microsoft.AspNetCore.Builder; + +public static class SwaggerBuilderExtensions { - public static class SwaggerBuilderExtensions + /// + /// Register the Swagger middleware with provided options + /// + public static IApplicationBuilder UseSwagger(this IApplicationBuilder app, SwaggerOptions options) { - /// - /// Register the Swagger middleware with provided options - /// - public static IApplicationBuilder UseSwagger(this IApplicationBuilder app, SwaggerOptions options) - { #if NET - return app.UseMiddleware(options, app.ApplicationServices.GetRequiredService()); + return app.UseMiddleware(options, app.ApplicationServices.GetRequiredService()); #else - return app.UseMiddleware(options); + return app.UseMiddleware(options); #endif - } + } - /// - /// Register the Swagger middleware with optional setup action for DI-injected options - /// - public static IApplicationBuilder UseSwagger( - this IApplicationBuilder app, - Action setupAction = null) + /// + /// Register the Swagger middleware with optional setup action for DI-injected options + /// + public static IApplicationBuilder UseSwagger( + this IApplicationBuilder app, + Action setupAction = null) + { + SwaggerOptions options; + using (var scope = app.ApplicationServices.CreateScope()) { - SwaggerOptions options; - using (var scope = app.ApplicationServices.CreateScope()) - { - options = scope.ServiceProvider.GetRequiredService>().Value; - setupAction?.Invoke(options); - } - - return app.UseSwagger(options); + options = scope.ServiceProvider.GetRequiredService>().Value; + setupAction?.Invoke(options); } + return app.UseSwagger(options); + } + #if NET - public static IEndpointConventionBuilder MapSwagger( - this IEndpointRouteBuilder endpoints, - string pattern = SwaggerOptions.DefaultRouteTemplate, - Action setupAction = null) + public static IEndpointConventionBuilder MapSwagger( + this IEndpointRouteBuilder endpoints, + string pattern = SwaggerOptions.DefaultRouteTemplate, + Action setupAction = null) + { + if (!RoutePatternFactory.Parse(pattern).Parameters.Any(x => x.Name == "documentName")) { - if (!RoutePatternFactory.Parse(pattern).Parameters.Any(x => x.Name == "documentName")) - { - throw new ArgumentException("Pattern must contain '{documentName}' parameter", nameof(pattern)); - } + throw new ArgumentException("Pattern must contain '{documentName}' parameter", nameof(pattern)); + } - var pipeline = endpoints.CreateApplicationBuilder() - .UseSwagger(Configure) - .Build(); + var pipeline = endpoints.CreateApplicationBuilder() + .UseSwagger(Configure) + .Build(); - return endpoints.MapGet(pattern, pipeline); + return endpoints.MapGet(pattern, pipeline); - void Configure(SwaggerOptions options) - { - var endpointOptions = new SwaggerEndpointOptions(); + void Configure(SwaggerOptions options) + { + var endpointOptions = new SwaggerEndpointOptions(); - setupAction?.Invoke(endpointOptions); + setupAction?.Invoke(endpointOptions); - options.RouteTemplate = pattern; - options.OpenApiVersion = endpointOptions.OpenApiVersion; - options.PreSerializeFilters.AddRange(endpointOptions.PreSerializeFilters); - } + options.RouteTemplate = pattern; + options.OpenApiVersion = endpointOptions.OpenApiVersion; + options.PreSerializeFilters.AddRange(endpointOptions.PreSerializeFilters); } -#endif } +#endif } diff --git a/src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/SwaggerOptionsExtensions.cs b/src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/SwaggerOptionsExtensions.cs index 805a5ece5a..563b21f76a 100644 --- a/src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/SwaggerOptionsExtensions.cs +++ b/src/Swashbuckle.AspNetCore.Swagger/DependencyInjection/SwaggerOptionsExtensions.cs @@ -19,10 +19,14 @@ public static void SetCustomDocumentSerializer( params object[] constructorParameters) where TDocumentSerializer : ISwaggerDocumentSerializer { +#if NET + ArgumentNullException.ThrowIfNull(swaggerOptions); +#else if (swaggerOptions == null) { throw new ArgumentNullException(nameof(swaggerOptions)); } +#endif swaggerOptions.CustomDocumentSerializer = (TDocumentSerializer)Activator.CreateInstance(typeof(TDocumentSerializer), constructorParameters); } } diff --git a/src/Swashbuckle.AspNetCore.Swagger/UnknownSwaggerDocument.cs b/src/Swashbuckle.AspNetCore.Swagger/UnknownSwaggerDocument.cs index cd5d4aa359..73a3e355f7 100644 --- a/src/Swashbuckle.AspNetCore.Swagger/UnknownSwaggerDocument.cs +++ b/src/Swashbuckle.AspNetCore.Swagger/UnknownSwaggerDocument.cs @@ -1,11 +1,8 @@ namespace Swashbuckle.AspNetCore.Swagger; -public class UnknownSwaggerDocument : InvalidOperationException -{ - public UnknownSwaggerDocument(string documentName, IEnumerable knownDocuments) - : base(string.Format("Unknown Swagger document - \"{0}\". Known Swagger documents: {1}", +public class UnknownSwaggerDocument(string documentName, IEnumerable knownDocuments) + : InvalidOperationException( + string.Format( + "Unknown Swagger document - \"{0}\". Known Swagger documents: {1}", documentName, - string.Join(",", knownDocuments?.Select(x => $"\"{x}\"")))) - { - } -} + string.Join(",", knownDocuments?.Select(x => $"\"{x}\"")))); diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSchemaGeneratorOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSchemaGeneratorOptions.cs index a2f38cfca5..6017437bf7 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSchemaGeneratorOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSchemaGeneratorOptions.cs @@ -4,18 +4,12 @@ namespace Swashbuckle.AspNetCore.SwaggerGen; -internal class ConfigureSchemaGeneratorOptions : IConfigureOptions +internal class ConfigureSchemaGeneratorOptions( + IOptions swaggerGenOptionsAccessor, + IServiceProvider serviceProvider) : IConfigureOptions { - private readonly SwaggerGenOptions _swaggerGenOptions; - private readonly IServiceProvider _serviceProvider; - - public ConfigureSchemaGeneratorOptions( - IOptions swaggerGenOptionsAccessor, - IServiceProvider serviceProvider) - { - _swaggerGenOptions = swaggerGenOptionsAccessor.Value; - _serviceProvider = serviceProvider; - } + private readonly SwaggerGenOptions _swaggerGenOptions = swaggerGenOptionsAccessor.Value; + private readonly IServiceProvider _serviceProvider = serviceProvider; public void Configure(SchemaGeneratorOptions options) { @@ -26,7 +20,7 @@ public void Configure(SchemaGeneratorOptions options) filterDescriptor => options.SchemaFilters.Add(GetOrCreateFilter(filterDescriptor))); } - private void DeepCopy(SchemaGeneratorOptions source, SchemaGeneratorOptions target) + private static void DeepCopy(SchemaGeneratorOptions source, SchemaGeneratorOptions target) { target.CustomTypeMappings = new Dictionary>(source.CustomTypeMappings); target.UseInlineDefinitionsForEnums = source.UseInlineDefinitionsForEnums; @@ -40,7 +34,7 @@ private void DeepCopy(SchemaGeneratorOptions source, SchemaGeneratorOptions targ target.UseAllOfToExtendReferenceSchemas = source.UseAllOfToExtendReferenceSchemas; target.SupportNonNullableReferenceTypes = source.SupportNonNullableReferenceTypes; target.NonNullableReferenceTypesAsRequired = source.NonNullableReferenceTypesAsRequired; - target.SchemaFilters = new List(source.SchemaFilters); + target.SchemaFilters = [.. source.SchemaFilters]; } private TFilter GetOrCreateFilter(FilterDescriptor filterDescriptor) diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGeneratorOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGeneratorOptions.cs index d64632b814..c2fdf8f759 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGeneratorOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/ConfigureSwaggerGeneratorOptions.cs @@ -1,29 +1,23 @@ -using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.OpenApi.Models; -#if !NET +#if NET +using Microsoft.AspNetCore.Hosting; +#else using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; #endif namespace Swashbuckle.AspNetCore.SwaggerGen; -internal class ConfigureSwaggerGeneratorOptions : IConfigureOptions +internal class ConfigureSwaggerGeneratorOptions( + IOptions swaggerGenOptionsAccessor, + IServiceProvider serviceProvider, + IWebHostEnvironment hostingEnv) : IConfigureOptions { - private readonly SwaggerGenOptions _swaggerGenOptions; - private readonly IServiceProvider _serviceProvider; - private readonly IWebHostEnvironment _hostingEnv; - - public ConfigureSwaggerGeneratorOptions( - IOptions swaggerGenOptionsAccessor, - IServiceProvider serviceProvider, - IWebHostEnvironment hostingEnv) - { - _swaggerGenOptions = swaggerGenOptionsAccessor.Value; - _serviceProvider = serviceProvider; - _hostingEnv = hostingEnv; - } + private readonly SwaggerGenOptions _swaggerGenOptions = swaggerGenOptionsAccessor.Value; + private readonly IServiceProvider _serviceProvider = serviceProvider; + private readonly IWebHostEnvironment _hostingEnv = hostingEnv; public void Configure(SwaggerGeneratorOptions options) { @@ -89,7 +83,7 @@ public void Configure(SwaggerGeneratorOptions options) } } - public void DeepCopy(SwaggerGeneratorOptions source, SwaggerGeneratorOptions target) + public static void DeepCopy(SwaggerGeneratorOptions source, SwaggerGeneratorOptions target) { target.SwaggerDocs = new Dictionary(source.SwaggerDocs); target.DocInclusionPredicate = source.DocInclusionPredicate; @@ -101,17 +95,17 @@ public void DeepCopy(SwaggerGeneratorOptions source, SwaggerGeneratorOptions tar target.InferSecuritySchemes = source.InferSecuritySchemes; target.DescribeAllParametersInCamelCase = source.DescribeAllParametersInCamelCase; target.SchemaComparer = source.SchemaComparer; - target.Servers = new List(source.Servers); + target.Servers = [.. source.Servers]; target.SecuritySchemes = new Dictionary(source.SecuritySchemes); - target.SecurityRequirements = new List(source.SecurityRequirements); - target.ParameterFilters = new List(source.ParameterFilters); - target.ParameterAsyncFilters = new List(source.ParameterAsyncFilters); - target.OperationFilters = new List(source.OperationFilters); - target.OperationAsyncFilters = new List(source.OperationAsyncFilters); - target.DocumentFilters = new List(source.DocumentFilters); - target.DocumentAsyncFilters = new List(source.DocumentAsyncFilters); - target.RequestBodyFilters = new List(source.RequestBodyFilters); - target.RequestBodyAsyncFilters = new List(source.RequestBodyAsyncFilters); + target.SecurityRequirements = [.. source.SecurityRequirements]; + target.ParameterFilters = [.. source.ParameterFilters]; + target.ParameterAsyncFilters = [.. source.ParameterAsyncFilters]; + target.OperationFilters = [.. source.OperationFilters]; + target.OperationAsyncFilters = [.. source.OperationAsyncFilters]; + target.DocumentFilters = [.. source.DocumentFilters]; + target.DocumentAsyncFilters = [.. source.DocumentAsyncFilters]; + target.RequestBodyFilters = [.. source.RequestBodyFilters]; + target.RequestBodyAsyncFilters = [.. source.RequestBodyAsyncFilters]; target.SecuritySchemesSelector = source.SecuritySchemesSelector; target.PathGroupSelector = source.PathGroupSelector; target.XmlCommentEndOfLine = source.XmlCommentEndOfLine; diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/DocumentProvider.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/DocumentProvider.cs index ee1f459a26..d46da87d53 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/DocumentProvider.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/DocumentProvider.cs @@ -5,26 +5,17 @@ namespace Microsoft.Extensions.ApiDescriptions; -internal class DocumentProvider : IDocumentProvider +internal class DocumentProvider( + IOptions generatorOptions, + IOptions options, + IAsyncSwaggerProvider swaggerProvider) : IDocumentProvider { - private readonly SwaggerGeneratorOptions _generatorOptions; - private readonly SwaggerOptions _options; - private readonly IAsyncSwaggerProvider _swaggerProvider; - - public DocumentProvider( - IOptions generatorOptions, - IOptions options, - IAsyncSwaggerProvider swaggerProvider) - { - _generatorOptions = generatorOptions.Value; - _options = options.Value; - _swaggerProvider = swaggerProvider; - } + private readonly SwaggerGeneratorOptions _generatorOptions = generatorOptions.Value; + private readonly SwaggerOptions _options = options.Value; + private readonly IAsyncSwaggerProvider _swaggerProvider = swaggerProvider; public IEnumerable GetDocumentNames() - { - return _generatorOptions.SwaggerDocs.Keys; - } + => _generatorOptions.SwaggerDocs.Keys; public async Task GenerateAsync(string documentName, TextWriter writer) { diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptions.cs index 3975935406..9e1924aeef 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptions.cs @@ -2,22 +2,22 @@ public class SwaggerGenOptions { - public SwaggerGeneratorOptions SwaggerGeneratorOptions { get; set; } = new SwaggerGeneratorOptions(); + public SwaggerGeneratorOptions SwaggerGeneratorOptions { get; set; } = new(); - public SchemaGeneratorOptions SchemaGeneratorOptions { get; set; } = new SchemaGeneratorOptions(); + public SchemaGeneratorOptions SchemaGeneratorOptions { get; set; } = new(); // NOTE: Filter instances can be added directly to the options exposed above OR they can be specified in // the following lists. In the latter case, they will be instantiated and added when options are injected // into their target services. This "deferred instantiation" allows the filters to be created from the // DI container, thus supporting contructor injection of services within filters. - public List ParameterFilterDescriptors { get; set; } = new List(); + public List ParameterFilterDescriptors { get; set; } = []; - public List RequestBodyFilterDescriptors { get; set; } = new List(); + public List RequestBodyFilterDescriptors { get; set; } = []; - public List OperationFilterDescriptors { get; set; } = new List(); + public List OperationFilterDescriptors { get; set; } = []; - public List DocumentFilterDescriptors { get; set; } = new List(); + public List DocumentFilterDescriptors { get; set; } = []; - public List SchemaFilterDescriptors { get; set; } = new List(); + public List SchemaFilterDescriptors { get; set; } = []; } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs index 53001fec25..cbcdccf3c2 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/DependencyInjection/SwaggerGenOptionsExtensions.cs @@ -78,7 +78,7 @@ public static void TagActionsBy( this SwaggerGenOptions swaggerGenOptions, Func tagSelector) { - swaggerGenOptions.SwaggerGeneratorOptions.TagsSelector = (apiDesc) => new[] { tagSelector(apiDesc) }; + swaggerGenOptions.SwaggerGeneratorOptions.TagsSelector = (apiDesc) => [tagSelector(apiDesc)]; } /// @@ -344,7 +344,11 @@ public static void SchemaFilter( params object[] arguments) where TFilter : ISchemaFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + swaggerGenOptions.SchemaFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -363,8 +367,16 @@ public static void AddSchemaFilterInstance( TFilter filterInstance) where TFilter : ISchemaFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); - if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + + if (filterInstance == null) + { + throw new ArgumentNullException(nameof(filterInstance)); + } + swaggerGenOptions.SchemaFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -383,7 +395,11 @@ public static void ParameterFilter( params object[] arguments) where TFilter : IParameterFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + swaggerGenOptions.ParameterFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -402,7 +418,11 @@ public static void ParameterAsyncFilter( params object[] arguments) where TFilter : IParameterAsyncFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + swaggerGenOptions.ParameterFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -421,8 +441,16 @@ public static void AddParameterFilterInstance( TFilter filterInstance) where TFilter : IParameterFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); - if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + + if (filterInstance == null) + { + throw new ArgumentNullException(nameof(filterInstance)); + } + swaggerGenOptions.ParameterFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -441,8 +469,16 @@ public static void AddParameterAsyncFilterInstance( TFilter filterInstance) where TFilter : IParameterAsyncFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); - if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + + if (filterInstance == null) + { + throw new ArgumentNullException(nameof(filterInstance)); + } + swaggerGenOptions.ParameterFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -461,7 +497,11 @@ public static void RequestBodyFilter( params object[] arguments) where TFilter : IRequestBodyFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + swaggerGenOptions.RequestBodyFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -480,7 +520,11 @@ public static void RequestBodyAsyncFilter( params object[] arguments) where TFilter : IRequestBodyAsyncFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + swaggerGenOptions.RequestBodyFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -499,8 +543,16 @@ public static void AddRequestBodyFilterInstance( TFilter filterInstance) where TFilter : IRequestBodyFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); - if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + + if (filterInstance == null) + { + throw new ArgumentNullException(nameof(filterInstance)); + } + swaggerGenOptions.RequestBodyFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -519,8 +571,16 @@ public static void AddRequestBodyAsyncFilterInstance( TFilter filterInstance) where TFilter : IRequestBodyAsyncFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); - if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + + if (filterInstance == null) + { + throw new ArgumentNullException(nameof(filterInstance)); + } + swaggerGenOptions.RequestBodyFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -539,7 +599,11 @@ public static void OperationFilter( params object[] arguments) where TFilter : IOperationFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + swaggerGenOptions.OperationFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -558,7 +622,11 @@ public static void OperationAsyncFilter( params object[] arguments) where TFilter : IOperationAsyncFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + swaggerGenOptions.OperationFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -577,8 +645,16 @@ public static void AddOperationFilterInstance( TFilter filterInstance) where TFilter : IOperationFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); - if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + + if (filterInstance == null) + { + throw new ArgumentNullException(nameof(filterInstance)); + } + swaggerGenOptions.OperationFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -597,8 +673,16 @@ public static void AddOperationAsyncFilterInstance( TFilter filterInstance) where TFilter : IOperationAsyncFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); - if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + + if (filterInstance == null) + { + throw new ArgumentNullException(nameof(filterInstance)); + } + swaggerGenOptions.OperationFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -617,7 +701,11 @@ public static void DocumentFilter( params object[] arguments) where TFilter : IDocumentFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + swaggerGenOptions.DocumentFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -637,7 +725,11 @@ public static void DocumentAsyncFilter( params object[] arguments) where TFilter : IDocumentAsyncFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + swaggerGenOptions.DocumentFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -656,8 +748,16 @@ public static void AddDocumentFilterInstance( TFilter filterInstance) where TFilter : IDocumentFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); - if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + + if (filterInstance == null) + { + throw new ArgumentNullException(nameof(filterInstance)); + } + swaggerGenOptions.DocumentFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -677,8 +777,16 @@ public static void AddDocumentAsyncFilterInstance( TFilter filterInstance) where TFilter : IDocumentAsyncFilter { - if (swaggerGenOptions == null) throw new ArgumentNullException(nameof(swaggerGenOptions)); - if (filterInstance == null) throw new ArgumentNullException(nameof(filterInstance)); + if (swaggerGenOptions == null) + { + throw new ArgumentNullException(nameof(swaggerGenOptions)); + } + + if (filterInstance == null) + { + throw new ArgumentNullException(nameof(filterInstance)); + } + swaggerGenOptions.DocumentFilterDescriptors.Add(new FilterDescriptor { Type = typeof(TFilter), @@ -709,7 +817,9 @@ public static void IncludeXmlComments( swaggerGenOptions.AddSchemaFilterInstance(new XmlCommentsSchemaFilter(xmlDocMembers, swaggerGenOptions.SwaggerGeneratorOptions)); if (includeControllerXmlComments) + { swaggerGenOptions.AddDocumentFilterInstance(new XmlCommentsDocumentFilter(xmlDocMembers, swaggerGenOptions.SwaggerGeneratorOptions)); + } } /// @@ -726,7 +836,9 @@ public static void IncludeXmlComments( string filePath, bool includeControllerXmlComments = false) { - swaggerGenOptions.IncludeXmlComments(() => new XPathDocument(filePath), includeControllerXmlComments); + swaggerGenOptions.IncludeXmlComments( + () => new XPathDocument(filePath), + includeControllerXmlComments); } /// diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/DataProperty.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/DataProperty.cs index 4d7cda277b..22f61bdf23 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/DataProperty.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/DataProperty.cs @@ -2,31 +2,20 @@ namespace Swashbuckle.AspNetCore.SwaggerGen; -public class DataProperty +public class DataProperty( + string name, + Type memberType, + bool isRequired = false, + bool isNullable = false, + bool isReadOnly = false, + bool isWriteOnly = false, + MemberInfo memberInfo = null) { - public DataProperty( - string name, - Type memberType, - bool isRequired = false, - bool isNullable = false, - bool isReadOnly = false, - bool isWriteOnly = false, - MemberInfo memberInfo = null) - { - Name = name; - IsRequired = isRequired; - IsNullable = isNullable; - IsReadOnly = isReadOnly; - IsWriteOnly = isWriteOnly; - MemberType = memberType; - MemberInfo = memberInfo; - } - - public string Name { get; } - public bool IsRequired { get; } - public bool IsNullable { get; } - public bool IsReadOnly { get; } - public bool IsWriteOnly { get; } - public Type MemberType { get; } - public MemberInfo MemberInfo { get; } + public string Name { get; } = name; + public bool IsRequired { get; } = isRequired; + public bool IsNullable { get; } = isNullable; + public bool IsReadOnly { get; } = isReadOnly; + public bool IsWriteOnly { get; } = isWriteOnly; + public Type MemberType { get; } = memberType; + public MemberInfo MemberInfo { get; } = memberInfo; } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/DataType.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/DataType.cs index 9c60153b80..3a1ddb134d 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/DataType.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/DataType.cs @@ -9,5 +9,5 @@ public enum DataType Array, Dictionary, Object, - Unknown + Unknown, } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs index 2eac583cc7..2cfb2140c0 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/JsonSerializerDataContractResolver.cs @@ -6,14 +6,9 @@ namespace Swashbuckle.AspNetCore.SwaggerGen; -public class JsonSerializerDataContractResolver : ISerializerDataContractResolver +public class JsonSerializerDataContractResolver(JsonSerializerOptions serializerOptions) : ISerializerDataContractResolver { - private readonly JsonSerializerOptions _serializerOptions; - - public JsonSerializerDataContractResolver(JsonSerializerOptions serializerOptions) - { - _serializerOptions = serializerOptions; - } + private readonly JsonSerializerOptions _serializerOptions = serializerOptions; public DataContract GetDataContractForType(Type type) { @@ -71,7 +66,12 @@ public DataContract GetDataContractForType(Type type) .Cast() .Select(value => JsonConverterFunc(value, keyType)); - keys = enumValuesAsJson.Any(json => json.StartsWith("\"")) + keys = +#if NET + enumValuesAsJson.Any(json => json.StartsWith('\"')) +#else + enumValuesAsJson.Any(json => json.StartsWith("\"")) +#endif ? enumValuesAsJson.Select(json => json.Replace("\"", string.Empty)) : keyType.GetEnumNames(); } @@ -190,7 +190,7 @@ private List GetDataPropertiesFor(Type objectType, out Type extens (property.IsPubliclyReadable() || property.IsPubliclyWritable()) && !(property.GetIndexParameters().Length > 0) && !(property.HasAttribute() && isIgnoredViaNet5Attribute) && - !(property.HasAttribute()) && + !property.HasAttribute() && !(_serializerOptions.IgnoreReadOnlyProperties && !property.IsPubliclyWritable()); }) .OrderBy(property => property.DeclaringType.GetInheritanceChain().Length); @@ -219,8 +219,15 @@ private List GetDataPropertiesFor(Type objectType, out Type extens var deserializationConstructor = propertyInfo.DeclaringType?.GetConstructors() .OrderBy(c => { - if (c.GetCustomAttribute() != null) return 1; - if (c.GetParameters().Length == 0) return 2; + if (c.GetCustomAttribute() != null) + { + return 1; + } + else if (c.GetParameters().Length == 0) + { + return 2; + } + return 3; }) .FirstOrDefault(); diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/MemberInfoExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/MemberInfoExtensions.cs index 22972a5877..4a5021eea1 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/MemberInfoExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/MemberInfoExtensions.cs @@ -181,7 +181,6 @@ private static bool GetNullableFallbackValue(this MemberInfo memberInfo) return false; } -#endif private static List GetDeclaringTypeChain(MemberInfo memberInfo) { @@ -196,4 +195,5 @@ private static List GetDeclaringTypeChain(MemberInfo memberInfo) return chain; } +#endif } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/OpenApiSchemaExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/OpenApiSchemaExtensions.cs index 3e0e24300b..209f491bc2 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/OpenApiSchemaExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/OpenApiSchemaExtensions.cs @@ -34,39 +34,47 @@ public static void ApplyValidationAttributes(this OpenApiSchema schema, IEnumera foreach (var attribute in customAttributes) { if (attribute is DataTypeAttribute dataTypeAttribute) + { ApplyDataTypeAttribute(schema, dataTypeAttribute); - + } else if (attribute is MinLengthAttribute minLengthAttribute) + { ApplyMinLengthAttribute(schema, minLengthAttribute); - + } else if (attribute is MaxLengthAttribute maxLengthAttribute) + { ApplyMaxLengthAttribute(schema, maxLengthAttribute); - + } #if NET - else if (attribute is LengthAttribute lengthAttribute) + { ApplyLengthAttribute(schema, lengthAttribute); - + } else if (attribute is Base64StringAttribute base64Attribute) + { ApplyBase64Attribute(schema); - + } #endif - else if (attribute is RangeAttribute rangeAttribute) + { ApplyRangeAttribute(schema, rangeAttribute); - + } else if (attribute is RegularExpressionAttribute regularExpressionAttribute) + { ApplyRegularExpressionAttribute(schema, regularExpressionAttribute); - + } else if (attribute is StringLengthAttribute stringLengthAttribute) + { ApplyStringLengthAttribute(schema, stringLengthAttribute); - + } else if (attribute is ReadOnlyAttribute readOnlyAttribute) + { ApplyReadOnlyAttribute(schema, readOnlyAttribute); - + } else if (attribute is DescriptionAttribute descriptionAttribute) + { ApplyDescriptionAttribute(schema, descriptionAttribute); - + } } } @@ -75,37 +83,49 @@ public static void ApplyRouteConstraints(this OpenApiSchema schema, ApiParameter foreach (var constraint in routeInfo.Constraints) { if (constraint is MinRouteConstraint minRouteConstraint) + { ApplyMinRouteConstraint(schema, minRouteConstraint); - + } else if (constraint is MaxRouteConstraint maxRouteConstraint) + { ApplyMaxRouteConstraint(schema, maxRouteConstraint); - + } else if (constraint is MinLengthRouteConstraint minLengthRouteConstraint) + { ApplyMinLengthRouteConstraint(schema, minLengthRouteConstraint); - + } else if (constraint is MaxLengthRouteConstraint maxLengthRouteConstraint) + { ApplyMaxLengthRouteConstraint(schema, maxLengthRouteConstraint); - + } else if (constraint is RangeRouteConstraint rangeRouteConstraint) + { ApplyRangeRouteConstraint(schema, rangeRouteConstraint); - + } else if (constraint is RegexRouteConstraint regexRouteConstraint) + { ApplyRegexRouteConstraint(schema, regexRouteConstraint); - + } else if (constraint is LengthRouteConstraint lengthRouteConstraint) + { ApplyLengthRouteConstraint(schema, lengthRouteConstraint); - + } else if (constraint is FloatRouteConstraint or DecimalRouteConstraint) + { schema.Type = JsonSchemaTypes.Number; - + } else if (constraint is LongRouteConstraint or IntRouteConstraint) + { schema.Type = JsonSchemaTypes.Integer; - + } else if (constraint is GuidRouteConstraint or StringRouteConstraint) + { schema.Type = JsonSchemaTypes.String; - + } else if (constraint is BoolRouteConstraint) + { schema.Type = JsonSchemaTypes.Boolean; + } } } @@ -139,33 +159,49 @@ private static void ApplyDataTypeAttribute(OpenApiSchema schema, DataTypeAttribu private static void ApplyMinLengthAttribute(OpenApiSchema schema, MinLengthAttribute minLengthAttribute) { if (schema.Type == JsonSchemaTypes.Array) + { schema.MinItems = minLengthAttribute.Length; + } else + { schema.MinLength = minLengthAttribute.Length; + } } private static void ApplyMinLengthRouteConstraint(OpenApiSchema schema, MinLengthRouteConstraint minLengthRouteConstraint) { if (schema.Type == JsonSchemaTypes.Array) + { schema.MinItems = minLengthRouteConstraint.MinLength; + } else + { schema.MinLength = minLengthRouteConstraint.MinLength; + } } private static void ApplyMaxLengthAttribute(OpenApiSchema schema, MaxLengthAttribute maxLengthAttribute) { if (schema.Type == JsonSchemaTypes.Array) + { schema.MaxItems = maxLengthAttribute.Length; + } else + { schema.MaxLength = maxLengthAttribute.Length; + } } private static void ApplyMaxLengthRouteConstraint(OpenApiSchema schema, MaxLengthRouteConstraint maxLengthRouteConstraint) { if (schema.Type == JsonSchemaTypes.Array) + { schema.MaxItems = maxLengthRouteConstraint.MaxLength; + } else + { schema.MaxLength = maxLengthRouteConstraint.MaxLength; + } } #if NET diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaFilterContext.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaFilterContext.cs index b5f223fb5a..0a460492c0 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaFilterContext.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaFilterContext.cs @@ -2,31 +2,22 @@ namespace Swashbuckle.AspNetCore.SwaggerGen; -public class SchemaFilterContext +public class SchemaFilterContext( + Type type, + ISchemaGenerator schemaGenerator, + SchemaRepository schemaRepository, + MemberInfo memberInfo = null, + ParameterInfo parameterInfo = null) { - public SchemaFilterContext( - Type type, - ISchemaGenerator schemaGenerator, - SchemaRepository schemaRepository, - MemberInfo memberInfo = null, - ParameterInfo parameterInfo = null) - { - Type = type; - SchemaGenerator = schemaGenerator; - SchemaRepository = schemaRepository; - MemberInfo = memberInfo; - ParameterInfo = parameterInfo; - } + public Type Type { get; } = type; - public Type Type { get; } + public ISchemaGenerator SchemaGenerator { get; } = schemaGenerator; - public ISchemaGenerator SchemaGenerator { get; } + public SchemaRepository SchemaRepository { get; } = schemaRepository; - public SchemaRepository SchemaRepository { get; } + public MemberInfo MemberInfo { get; } = memberInfo; - public MemberInfo MemberInfo { get; } - - public ParameterInfo ParameterInfo { get; } + public ParameterInfo ParameterInfo { get; } = parameterInfo; public string DocumentName => SchemaRepository.DocumentName; } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs index 17f4f5e13c..c33fa84b92 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGenerator.cs @@ -11,18 +11,12 @@ namespace Swashbuckle.AspNetCore.SwaggerGen; -public class SchemaGenerator : ISchemaGenerator +public class SchemaGenerator( + SchemaGeneratorOptions generatorOptions, + ISerializerDataContractResolver serializerDataContractResolver) : ISchemaGenerator { - private readonly SchemaGeneratorOptions _generatorOptions; - private readonly ISerializerDataContractResolver _serializerDataContractResolver; - - public SchemaGenerator( - SchemaGeneratorOptions generatorOptions, - ISerializerDataContractResolver serializerDataContractResolver) - { - _generatorOptions = generatorOptions; - _serializerDataContractResolver = serializerDataContractResolver; - } + private readonly SchemaGeneratorOptions _generatorOptions = generatorOptions; + private readonly ISerializerDataContractResolver _serializerDataContractResolver = serializerDataContractResolver; [Obsolete($"{nameof(IOptions)} is no longer used. This constructor will be removed in a future major release.")] public SchemaGenerator( @@ -41,12 +35,17 @@ public OpenApiSchema GenerateSchema( ApiParameterRouteInfo routeInfo = null) { if (memberInfo != null) + { return GenerateSchemaForMember(modelType, schemaRepository, memberInfo); - - if (parameterInfo != null) + } + else if (parameterInfo != null) + { return GenerateSchemaForParameter(modelType, schemaRepository, parameterInfo, routeInfo); - - return GenerateSchemaForType(modelType, schemaRepository); + } + else + { + return GenerateSchemaForType(modelType, schemaRepository); + } } private OpenApiSchema GenerateSchemaForMember( @@ -196,17 +195,23 @@ private bool IsBaseTypeWithKnownTypesDefined(DataContract dataContract, out IEnu { knownTypesDataContracts = null; - if (dataContract.DataType != DataType.Object) return false; + if (dataContract.DataType != DataType.Object) + { + return false; + } var subTypes = _generatorOptions.SubTypesSelector(dataContract.UnderlyingType); - if (!subTypes.Any()) return false; + if (!subTypes.Any()) + { + return false; + } var knownTypes = !dataContract.UnderlyingType.IsAbstract ? new[] { dataContract.UnderlyingType }.Union(subTypes) : subTypes; - knownTypesDataContracts = knownTypes.Select(knownType => GetDataContractFor(knownType)); + knownTypesDataContracts = knownTypes.Select(GetDataContractFor); return true; } @@ -293,8 +298,9 @@ private OpenApiSchema GenerateConcreteSchema(DataContract dataContract, SchemaRe private bool TryGetCustomTypeMapping(Type modelType, out Func schemaFactory) { - return _generatorOptions.CustomTypeMappings.TryGetValue(modelType, out schemaFactory) - || (modelType.IsConstructedGenericType && _generatorOptions.CustomTypeMappings.TryGetValue(modelType.GetGenericTypeDefinition(), out schemaFactory)); + return + _generatorOptions.CustomTypeMappings.TryGetValue(modelType, out schemaFactory) || + (modelType.IsConstructedGenericType && _generatorOptions.CustomTypeMappings.TryGetValue(modelType.GetGenericTypeDefinition(), out schemaFactory)); } private static OpenApiSchema CreatePrimitiveSchema(DataContract dataContract) @@ -309,11 +315,10 @@ private static OpenApiSchema CreatePrimitiveSchema(DataContract dataContract) // For backwards compatibility only - EnumValues is obsolete if (dataContract.EnumValues != null) { - schema.Enum = dataContract.EnumValues + schema.Enum = [.. dataContract.EnumValues .Select(value => JsonSerializer.Serialize(value)) .Distinct() - .Select(JsonModelFactory.CreateFromJson) - .ToList(); + .Select(JsonModelFactory.CreateFromJson)]; return schema; } @@ -321,12 +326,11 @@ private static OpenApiSchema CreatePrimitiveSchema(DataContract dataContract) if (dataContract.UnderlyingType.IsEnum) { - schema.Enum = dataContract.UnderlyingType.GetEnumValues() + schema.Enum = [.. dataContract.UnderlyingType.GetEnumValues() .Cast() .Select(value => dataContract.JsonConverter(value)) .Distinct() - .Select(JsonModelFactory.CreateFromJson) - .ToList(); + .Select(JsonModelFactory.CreateFromJson)]; } return schema; @@ -334,8 +338,9 @@ private static OpenApiSchema CreatePrimitiveSchema(DataContract dataContract) private OpenApiSchema CreateArraySchema(DataContract dataContract, SchemaRepository schemaRepository) { - var hasUniqueItems = dataContract.UnderlyingType.IsConstructedFrom(typeof(ISet<>), out _) - || dataContract.UnderlyingType.IsConstructedFrom(typeof(KeyedCollection<,>), out _); + var hasUniqueItems = + dataContract.UnderlyingType.IsConstructedFrom(typeof(ISet<>), out _) || + dataContract.UnderlyingType.IsConstructedFrom(typeof(KeyedCollection<,>), out _); return new OpenApiSchema { @@ -425,14 +430,17 @@ private OpenApiSchema CreateObjectSchema(DataContract dataContract, SchemaReposi var customAttributes = dataProperty.MemberInfo?.GetInlineAndMetadataAttributes() ?? []; if (_generatorOptions.IgnoreObsoleteProperties && customAttributes.OfType().Any()) + { continue; + } schema.Properties[dataProperty.Name] = (dataProperty.MemberInfo != null) ? GenerateSchemaForMember(dataProperty.MemberType, schemaRepository, dataProperty.MemberInfo, dataProperty) : GenerateSchemaForType(dataProperty.MemberType, schemaRepository); - var markNonNullableTypeAsRequired = _generatorOptions.NonNullableReferenceTypesAsRequired - && (dataProperty.MemberInfo?.IsNonNullableReferenceType() ?? false); + var markNonNullableTypeAsRequired = + _generatorOptions.NonNullableReferenceTypesAsRequired && + (dataProperty.MemberInfo?.IsNonNullableReferenceType() ?? false); if (( dataProperty.IsRequired @@ -501,12 +509,14 @@ private bool TryGetDiscriminatorFor( foreach (var knownTypeDataContract in knownTypesDataContracts) { - var discriminatorValue = _generatorOptions.DiscriminatorValueSelector(knownTypeDataContract.UnderlyingType) + var discriminatorValue = + _generatorOptions.DiscriminatorValueSelector(knownTypeDataContract.UnderlyingType) ?? knownTypeDataContract.ObjectTypeNameValue; - if (discriminatorValue == null) continue; - - discriminator.Mapping.Add(discriminatorValue, GenerateConcreteSchema(knownTypeDataContract, schemaRepository).Reference.ReferenceV3); + if (discriminatorValue != null) + { + discriminator.Mapping.Add(discriminatorValue, GenerateConcreteSchema(knownTypeDataContract, schemaRepository).Reference.ReferenceV3); + } } return true; @@ -518,13 +528,16 @@ private OpenApiSchema GenerateReferencedSchema( Func definitionFactory) { if (schemaRepository.TryLookupByType(dataContract.UnderlyingType, out OpenApiSchema referenceSchema)) + { return referenceSchema; + } var schemaId = _generatorOptions.SchemaIdSelector(dataContract.UnderlyingType); schemaRepository.RegisterType(dataContract.UnderlyingType, schemaId); var schema = definitionFactory(); + ApplyFilters(schema, dataContract.UnderlyingType, schemaRepository); return schemaRepository.AddDefinition(schemaId, schema); @@ -560,6 +573,7 @@ private Microsoft.OpenApi.Any.IOpenApiAny GenerateDefaultValue( // See https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2885 and // https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2904. var defaultValueType = defaultValue?.GetType(); + if (defaultValueType != null && defaultValueType != modelType) { dataContract = GetDataContractFor(defaultValueType); diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGeneratorOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGeneratorOptions.cs index 2b68c357f0..b571da2e64 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGeneratorOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/SchemaGeneratorOptions.cs @@ -11,7 +11,7 @@ public SchemaGeneratorOptions() SubTypesSelector = DefaultSubTypesSelector; DiscriminatorNameSelector = DefaultDiscriminatorNameSelector; DiscriminatorValueSelector = DefaultDiscriminatorValueSelector; - SchemaFilters = new List(); + SchemaFilters = []; } public IDictionary> CustomTypeMappings { get; set; } @@ -45,24 +45,16 @@ private string DefaultSchemaIdSelector(Type modelType) if (!modelType.IsConstructedGenericType) return modelType.Name.Replace("[]", "Array"); var prefix = modelType.GetGenericArguments() - .Select(genericArg => DefaultSchemaIdSelector(genericArg)) + .Select(DefaultSchemaIdSelector) .Aggregate((previous, current) => previous + current); return prefix + modelType.Name.Split('`').First(); } private IEnumerable DefaultSubTypesSelector(Type baseType) - { - return baseType.Assembly.GetTypes().Where(type => type.IsSubclassOf(baseType)); - } + => baseType.Assembly.GetTypes().Where(type => type.IsSubclassOf(baseType)); - private string DefaultDiscriminatorNameSelector(Type baseType) - { - return null; - } + private string DefaultDiscriminatorNameSelector(Type baseType) => null; - private string DefaultDiscriminatorValueSelector(Type subType) - { - return null; - } + private string DefaultDiscriminatorValueSelector(Type subType) => null; } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/TypeExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/TypeExtensions.cs index e2ee7ec667..6b18161c52 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/TypeExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SchemaGenerator/TypeExtensions.cs @@ -29,7 +29,7 @@ public static bool IsConstructedFrom(this Type type, Type genericType, out Type public static bool IsReferenceOrNullableType(this Type type) { - return (!type.IsValueType || Nullable.GetUnderlyingType(type) != null); + return !type.IsValueType || Nullable.GetUnderlyingType(type) != null; } public static object GetDefaultValue(this Type type) @@ -41,7 +41,10 @@ public static object GetDefaultValue(this Type type) public static Type[] GetInheritanceChain(this Type type) { - if (type.IsInterface) { return type.GetInterfaces(); } + if (type.IsInterface) + { + return type.GetInterfaces(); + } var inheritanceChain = new List(); @@ -52,6 +55,6 @@ public static Type[] GetInheritanceChain(this Type type) current = current.BaseType; } - return inheritanceChain.ToArray(); + return [.. inheritanceChain]; } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiDescriptionExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiDescriptionExtensions.cs index da146a5cf1..00dcf23542 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiDescriptionExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiDescriptionExtensions.cs @@ -50,11 +50,11 @@ public static void GetAdditionalMetadata(this ApiDescription apiDescription, { customAttributes = methodInfo.GetCustomAttributes(true) .Union(methodInfo.DeclaringType.GetCustomAttributes(true)); - - return; } - - customAttributes = []; + else + { + customAttributes = []; + } } internal static string RelativePathSansParameterConstraints(this ApiDescription apiDescription) @@ -63,6 +63,11 @@ internal static string RelativePathSansParameterConstraints(this ApiDescription var sanitizedSegments = routeTemplate .Segments .Select(s => string.Concat(s.Parts.Select(p => p.Name != null ? $"{{{p.Name}}}" : p.Text))); + +#if NET + return string.Join('/', sanitizedSegments); +#else return string.Join("/", sanitizedSegments); +#endif } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiParameterDescriptionExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiParameterDescriptionExtensions.cs index 34825aff0a..c52b77ecfc 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiParameterDescriptionExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ApiParameterDescriptionExtensions.cs @@ -10,16 +10,16 @@ namespace Swashbuckle.AspNetCore.SwaggerGen; public static class ApiParameterDescriptionExtensions { - private static readonly Type[] RequiredAttributeTypes = new[] - { + private static readonly Type[] RequiredAttributeTypes = + [ typeof(BindRequiredAttribute), typeof(RequiredAttribute), #if NET typeof(System.Runtime.CompilerServices.RequiredMemberAttribute) #endif - }; + ]; - private static readonly HashSet IllegalHeaderParameters = new HashSet(StringComparer.OrdinalIgnoreCase) + private static readonly HashSet IllegalHeaderParameters = new(StringComparer.OrdinalIgnoreCase) { HeaderNames.Accept, HeaderNames.Authorization, @@ -47,9 +47,9 @@ public static bool IsRequiredParameter(this ApiParameterDescription apiParameter // For non-controllers, prefer the IsRequired flag if we're not on netstandard 2.0, otherwise fallback to the default logic. return #if NET - apiParameter.IsRequired; + apiParameter.IsRequired; #else - IsRequired(); + IsRequired(); #endif } @@ -75,12 +75,18 @@ public static PropertyInfo PropertyInfo(this ApiParameterDescription apiParamete public static IEnumerable CustomAttributes(this ApiParameterDescription apiParameter) { var propertyInfo = apiParameter.PropertyInfo(); - if (propertyInfo != null) return propertyInfo.GetCustomAttributes(true); + if (propertyInfo != null) + { + return propertyInfo.GetCustomAttributes(true); + } var parameterInfo = apiParameter.ParameterInfo(); - if (parameterInfo != null) return parameterInfo.GetCustomAttributes(true); + if (parameterInfo != null) + { + return parameterInfo.GetCustomAttributes(true); + } - return Enumerable.Empty(); + return []; } [Obsolete("Use ParameterInfo(), PropertyInfo() and CustomAttributes() extension methods instead")] @@ -120,8 +126,10 @@ internal static bool IsFromForm(this ApiParameterDescription apiParameter) var source = apiParameter.Source; var elementType = isEnhancedModelMetadataSupported ? apiParameter.ModelMetadata?.ElementType : null; - return (source == BindingSource.Form || source == BindingSource.FormFile) - || (elementType != null && typeof(IFormFile).IsAssignableFrom(elementType)); + return + source == BindingSource.Form || + source == BindingSource.FormFile || + (elementType != null && typeof(IFormFile).IsAssignableFrom(elementType)); } internal static bool IsIllegalHeaderParameter(this ApiParameterDescription apiParameter) diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/DocumentFilterContext.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/DocumentFilterContext.cs index 32fb1652e8..dc8e3f6a7e 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/DocumentFilterContext.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/DocumentFilterContext.cs @@ -2,23 +2,16 @@ namespace Swashbuckle.AspNetCore.SwaggerGen; -public class DocumentFilterContext +public class DocumentFilterContext( + IEnumerable apiDescriptions, + ISchemaGenerator schemaGenerator, + SchemaRepository schemaRepository) { - public DocumentFilterContext( - IEnumerable apiDescriptions, - ISchemaGenerator schemaGenerator, - SchemaRepository schemaRepository) - { - ApiDescriptions = apiDescriptions; - SchemaGenerator = schemaGenerator; - SchemaRepository = schemaRepository; - } + public IEnumerable ApiDescriptions { get; } = apiDescriptions; - public IEnumerable ApiDescriptions { get; } + public ISchemaGenerator SchemaGenerator { get; } = schemaGenerator; - public ISchemaGenerator SchemaGenerator { get; } - - public SchemaRepository SchemaRepository { get; } + public SchemaRepository SchemaRepository { get; } = schemaRepository; public string DocumentName => SchemaRepository.DocumentName; } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/IFileResult.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/IFileResult.cs index b340931489..31d1c883e3 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/IFileResult.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/IFileResult.cs @@ -1,5 +1,3 @@ namespace Swashbuckle.AspNetCore.SwaggerGen; -internal interface IFileResult -{ -} +internal interface IFileResult; diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/OpenApiAnyFactory.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/OpenApiAnyFactory.cs index 04ee13b55b..6237c82029 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/OpenApiAnyFactory.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/OpenApiAnyFactory.cs @@ -48,34 +48,52 @@ private static OpenApiObject CreateOpenApiObject(JsonElement jsonElement) private static IOpenApiAny CreateFromJsonElement(JsonElement jsonElement) { if (jsonElement.ValueKind == JsonValueKind.Null) + { return new OpenApiNull(); + } if (jsonElement.ValueKind == JsonValueKind.True || jsonElement.ValueKind == JsonValueKind.False) + { return new OpenApiBoolean(jsonElement.GetBoolean()); + } if (jsonElement.ValueKind == JsonValueKind.Number) { if (jsonElement.TryGetInt32(out int intValue)) + { return new OpenApiInteger(intValue); + } if (jsonElement.TryGetInt64(out long longValue)) + { return new OpenApiLong(longValue); + } if (jsonElement.TryGetSingle(out float floatValue) && !float.IsInfinity(floatValue)) + { return new OpenApiFloat(floatValue); + } if (jsonElement.TryGetDouble(out double doubleValue)) + { return new OpenApiDouble(doubleValue); + } } if (jsonElement.ValueKind == JsonValueKind.String) + { return new OpenApiString(jsonElement.ToString()); + } if (jsonElement.ValueKind == JsonValueKind.Array) + { return CreateOpenApiArray(jsonElement); + } if (jsonElement.ValueKind == JsonValueKind.Object) + { return CreateOpenApiObject(jsonElement); + } throw new ArgumentException($"Unsupported value kind {jsonElement.ValueKind}"); } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/OperationFilterContext.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/OperationFilterContext.cs index ff409f3180..e71059c169 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/OperationFilterContext.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/OperationFilterContext.cs @@ -3,27 +3,19 @@ namespace Swashbuckle.AspNetCore.SwaggerGen; -public class OperationFilterContext +public class OperationFilterContext( + ApiDescription apiDescription, + ISchemaGenerator schemaRegistry, + SchemaRepository schemaRepository, + MethodInfo methodInfo) { - public OperationFilterContext( - ApiDescription apiDescription, - ISchemaGenerator schemaRegistry, - SchemaRepository schemaRepository, - MethodInfo methodInfo) - { - ApiDescription = apiDescription; - SchemaGenerator = schemaRegistry; - SchemaRepository = schemaRepository; - MethodInfo = methodInfo; - } + public ApiDescription ApiDescription { get; } = apiDescription; - public ApiDescription ApiDescription { get; } + public ISchemaGenerator SchemaGenerator { get; } = schemaRegistry; - public ISchemaGenerator SchemaGenerator { get; } + public SchemaRepository SchemaRepository { get; } = schemaRepository; - public SchemaRepository SchemaRepository { get; } - - public MethodInfo MethodInfo { get; } + public MethodInfo MethodInfo { get; } = methodInfo; public string DocumentName => SchemaRepository.DocumentName; } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ParameterFilterContext.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ParameterFilterContext.cs index ea95936ccb..7defd60774 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ParameterFilterContext.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/ParameterFilterContext.cs @@ -3,31 +3,22 @@ namespace Swashbuckle.AspNetCore.SwaggerGen; -public class ParameterFilterContext +public class ParameterFilterContext( + ApiParameterDescription apiParameterDescription, + ISchemaGenerator schemaGenerator, + SchemaRepository schemaRepository, + PropertyInfo propertyInfo = null, + ParameterInfo parameterInfo = null) { - public ParameterFilterContext( - ApiParameterDescription apiParameterDescription, - ISchemaGenerator schemaGenerator, - SchemaRepository schemaRepository, - PropertyInfo propertyInfo = null, - ParameterInfo parameterInfo = null) - { - ApiParameterDescription = apiParameterDescription; - SchemaGenerator = schemaGenerator; - SchemaRepository = schemaRepository; - PropertyInfo = propertyInfo; - ParameterInfo = parameterInfo; - } + public ApiParameterDescription ApiParameterDescription { get; } = apiParameterDescription; - public ApiParameterDescription ApiParameterDescription { get; } + public ISchemaGenerator SchemaGenerator { get; } = schemaGenerator; - public ISchemaGenerator SchemaGenerator { get; } + public SchemaRepository SchemaRepository { get; } = schemaRepository; - public SchemaRepository SchemaRepository { get; } + public PropertyInfo PropertyInfo { get; } = propertyInfo; - public PropertyInfo PropertyInfo { get; } - - public ParameterInfo ParameterInfo { get; } + public ParameterInfo ParameterInfo { get; } = parameterInfo; public string DocumentName => SchemaRepository.DocumentName; } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/RequestBodyFilterContext.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/RequestBodyFilterContext.cs index c6db205846..fd01cd3552 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/RequestBodyFilterContext.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/RequestBodyFilterContext.cs @@ -2,27 +2,19 @@ namespace Swashbuckle.AspNetCore.SwaggerGen; -public class RequestBodyFilterContext +public class RequestBodyFilterContext( + ApiParameterDescription bodyParameterDescription, + IEnumerable formParameterDescriptions, + ISchemaGenerator schemaGenerator, + SchemaRepository schemaRepository) { - public RequestBodyFilterContext( - ApiParameterDescription bodyParameterDescription, - IEnumerable formParameterDescriptions, - ISchemaGenerator schemaGenerator, - SchemaRepository schemaRepository) - { - BodyParameterDescription = bodyParameterDescription; - FormParameterDescriptions = formParameterDescriptions; - SchemaGenerator = schemaGenerator; - SchemaRepository = schemaRepository; - } + public ApiParameterDescription BodyParameterDescription { get; } = bodyParameterDescription; - public ApiParameterDescription BodyParameterDescription { get; } + public IEnumerable FormParameterDescriptions { get; } = formParameterDescriptions; - public IEnumerable FormParameterDescriptions { get; } + public ISchemaGenerator SchemaGenerator { get; } = schemaGenerator; - public ISchemaGenerator SchemaGenerator { get; } - - public SchemaRepository SchemaRepository { get; } + public SchemaRepository SchemaRepository { get; } = schemaRepository; public string DocumentName => SchemaRepository.DocumentName; } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SchemaRepository.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SchemaRepository.cs index 98b2438cba..82753cc036 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SchemaRepository.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SchemaRepository.cs @@ -2,18 +2,13 @@ namespace Swashbuckle.AspNetCore.SwaggerGen; -public class SchemaRepository +public class SchemaRepository(string documentName = null) { - private readonly Dictionary _reservedIds = new Dictionary(); + private readonly Dictionary _reservedIds = []; - public SchemaRepository(string documentName = null) - { - DocumentName = documentName; - } - - public string DocumentName { get; } + public string DocumentName { get; } = documentName; - public Dictionary Schemas { get; private set; } = new Dictionary(); + public Dictionary Schemas { get; private set; } = []; public void RegisterType(Type type, string schemaId) { diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/StringExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/StringExtensions.cs index 9ef9ba7976..d2aa7a4b62 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/StringExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/StringExtensions.cs @@ -4,11 +4,23 @@ internal static class StringExtensions { internal static string ToCamelCase(this string value) { - if (string.IsNullOrEmpty(value)) return value; + if (string.IsNullOrEmpty(value)) + { + return value; + } - var cameCasedParts = value.Split('.') +#if NET + var cameCasedParts = value + .Split('.') + .Select(part => char.ToLowerInvariant(part[0]) + part[1..]); + + return string.Join('.', cameCasedParts); +#else + var cameCasedParts = value + .Split('.') .Select(part => char.ToLowerInvariant(part[0]) + part.Substring(1)); return string.Join(".", cameCasedParts); +#endif } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs index ebf4893421..51eead04bf 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs @@ -46,7 +46,6 @@ public async Task GetSwaggerAsync( swaggerDoc.Paths = await GeneratePathsAsync(swaggerDoc, filterContext.ApiDescriptions, filterContext.SchemaRepository); swaggerDoc.Components.SecuritySchemes = await GetSecuritySchemesAsync(); - // NOTE: Filter processing moved here so they may affect generated security schemes foreach (var filter in _options.DocumentAsyncFilters) { await filter.ApplyAsync(swaggerDoc, filterContext, CancellationToken.None); @@ -71,7 +70,6 @@ public OpenApiDocument GetSwagger(string documentName, string host = null, strin swaggerDoc.Paths = GeneratePaths(swaggerDoc, filterContext.ApiDescriptions, filterContext.SchemaRepository); swaggerDoc.Components.SecuritySchemes = GetSecuritySchemesAsync().Result; - // NOTE: Filter processing moved here so they may affect generated security schemes foreach (var filter in _options.DocumentFilters) { filter.Apply(swaggerDoc, filterContext); @@ -102,7 +100,7 @@ public OpenApiDocument GetSwagger(string documentName, string host = null, strin } } - public IList GetDocumentNames() => _options.SwaggerDocs.Keys.ToList(); + public IList GetDocumentNames() => [.. _options.SwaggerDocs.Keys]; private void SortSchemas(OpenApiDocument document) { @@ -136,7 +134,7 @@ private void SortSchemas(OpenApiDocument document) { Schemas = schemaRepository.Schemas, }, - SecurityRequirements = new List(_options.SecurityRequirements) + SecurityRequirements = [.. _options.SecurityRequirements] }; return (new DocumentFilterContext(applicableApiDescriptions, _schemaGenerator, schemaRepository), swaggerDoc); @@ -176,10 +174,10 @@ private List GenerateServers(string host, string basePath) { if (_options.Servers.Count > 0) { - return new List(_options.Servers); + return [.. _options.Servers]; } - return (host == null && basePath == null) + return host == null && basePath == null ? [] : [new() { Url = $"{host}{basePath}" }]; } @@ -197,7 +195,8 @@ private async Task GeneratePathsAsync( var paths = new OpenApiPaths(); foreach (var group in apiDescriptionsByPath) { - paths.Add($"/{group.Key}", + paths.Add( + $"/{group.Key}", new OpenApiPathItem { Operations = await operationsGenerator(document, group, schemaRepository) @@ -291,10 +290,12 @@ private async Task> GenerateOperatio string.Join(", ", group.Select(apiDesc => apiDesc.ActionDescriptor.DisplayName)))); } - var apiDescription = (count > 1) ? _options.ConflictingActionsResolver(group) : group.Single(); + var apiDescription = + count > 1 ? + _options.ConflictingActionsResolver(group) : + group.Single(); - var normalizedMethod = httpMethod.ToUpperInvariant(); - if (!OperationTypeMap.TryGetValue(normalizedMethod, out var operationType)) + if (!OperationTypeMap.TryGetValue(httpMethod, out var operationType)) { // See https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2600 and // https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2740. @@ -522,8 +523,8 @@ private static async Task> GenerateParametersAsync( .Where(apiParam => { return !apiParam.IsFromBody() && !apiParam.IsFromForm() - && (!apiParam.CustomAttributes().OfType().Any()) - && (!apiParam.CustomAttributes().OfType().Any()) + && !apiParam.CustomAttributes().OfType().Any() + && !apiParam.CustomAttributes().OfType().Any() && (apiParam.ModelMetadata == null || apiParam.ModelMetadata.IsBindingAllowed) && !apiParam.IsIllegalHeaderParameter(); }); @@ -564,8 +565,9 @@ private OpenApiParameter GenerateParameterWithoutFilter( ? apiParameter.Name.ToCamelCase() : apiParameter.Name; - var location = apiParameter.Source != null && - ParameterLocationMap.TryGetValue(apiParameter.Source, out var value) + var location = + apiParameter.Source != null && + ParameterLocationMap.TryGetValue(apiParameter.Source, out var value) ? value : ParameterLocation.Query; @@ -573,10 +575,10 @@ private OpenApiParameter GenerateParameterWithoutFilter( var type = apiParameter.ModelMetadata?.ModelType; - if (type is not null - && type == typeof(string) - && apiParameter.Type is not null - && (Nullable.GetUnderlyingType(apiParameter.Type) ?? apiParameter.Type).IsEnum) + if (type is not null && + type == typeof(string) && + apiParameter.Type is not null && + (Nullable.GetUnderlyingType(apiParameter.Type) ?? apiParameter.Type).IsEnum) { type = apiParameter.Type; } @@ -591,9 +593,9 @@ private OpenApiParameter GenerateParameterWithoutFilter( : new OpenApiSchema { Type = JsonSchemaTypes.String }; var description = schema.Description; - if (string.IsNullOrEmpty(description) - && !string.IsNullOrEmpty(schema?.Reference?.Id) - && schemaRepository.Schemas.TryGetValue(schema.Reference.Id, out var openApiSchema)) + if (string.IsNullOrEmpty(description) && + !string.IsNullOrEmpty(schema?.Reference?.Id) && + schemaRepository.Schemas.TryGetValue(schema.Reference.Id, out var openApiSchema)) { description = openApiSchema.Description; } @@ -611,8 +613,10 @@ private OpenApiParameter GenerateParameterWithoutFilter( private static ParameterStyle? GetParameterStyle(Type type, BindingSource source) { - return source == BindingSource.Query && type?.IsGenericType == true && - typeof(IEnumerable>).IsAssignableFrom(type) + return + source == BindingSource.Query && + type?.IsGenericType == true && + typeof(IEnumerable>).IsAssignableFrom(type) ? ParameterStyle.DeepObject : null; } @@ -779,25 +783,29 @@ private OpenApiRequestBody GenerateRequestBodyFromBodyParameter( return new OpenApiRequestBody { - Content = contentTypes - .ToDictionary( - contentType => contentType, - contentType => new OpenApiMediaType - { - Schema = schema - } - ), - Required = isRequired + Required = isRequired, + Content = contentTypes.ToDictionary( + contentType => contentType, + contentType => new OpenApiMediaType + { + Schema = schema + }), }; } private static IEnumerable InferRequestContentTypes(ApiDescription apiDescription) { // If there's content types explicitly specified via ConsumesAttribute, use them - var explicitContentTypes = apiDescription.CustomAttributes().OfType() + var explicitContentTypes = apiDescription + .CustomAttributes() + .OfType() .SelectMany(attr => attr.ContentTypes) .Distinct(); - if (explicitContentTypes.Any()) return explicitContentTypes; + + if (explicitContentTypes.Any()) + { + return explicitContentTypes; + } // If there's content types surfaced by ApiExplorer, use them return apiDescription.SupportedRequestFormats @@ -826,18 +834,16 @@ private OpenApiRequestBody GenerateRequestBodyFromFormParameters( return new OpenApiRequestBody { - Content = contentTypes - .ToDictionary( - contentType => contentType, - contentType => new OpenApiMediaType - { - Schema = schema, - Encoding = totalProperties.ToDictionary( - entry => entry.Key, - entry => new OpenApiEncoding { Style = ParameterStyle.Form } - ) - } - ) + Content = contentTypes.ToDictionary( + contentType => contentType, + contentType => new OpenApiMediaType + { + Schema = schema, + Encoding = totalProperties.ToDictionary( + entry => entry.Key, + entry => new OpenApiEncoding { Style = ParameterStyle.Form } + ) + }) }; } @@ -848,26 +854,30 @@ private OpenApiSchema GenerateSchemaFromFormParameters( var properties = new Dictionary(); var requiredPropertyNames = new List(); var ownSchemas = new List(); + foreach (var formParameter in formParameters) { var propertyInfo = formParameter.PropertyInfo(); if (!propertyInfo?.HasAttribute() ?? true) { - var schema = (formParameter.ModelMetadata != null) - ? GenerateSchema( - formParameter.ModelMetadata.ModelType, - schemaRepository, - propertyInfo, - formParameter.ParameterInfo()) - : new OpenApiSchema { Type = JsonSchemaTypes.String }; - - if (schema.Reference is null - || (formParameter.ModelMetadata?.ModelType is not null && (Nullable.GetUnderlyingType(formParameter.ModelMetadata.ModelType) ?? formParameter.ModelMetadata.ModelType).IsEnum)) + var schema = + formParameter.ModelMetadata != null + ? GenerateSchema( + formParameter.ModelMetadata.ModelType, + schemaRepository, + propertyInfo, + formParameter.ParameterInfo()) + : new OpenApiSchema { Type = JsonSchemaTypes.String }; + + if (schema.Reference is null || + (formParameter.ModelMetadata?.ModelType is not null && (Nullable.GetUnderlyingType(formParameter.ModelMetadata.ModelType) ?? formParameter.ModelMetadata.ModelType).IsEnum)) { var name = _options.DescribeAllParametersInCamelCase ? formParameter.Name.ToCamelCase() : formParameter.Name; + properties.Add(name, schema); + if (formParameter.IsRequiredParameter()) { requiredPropertyNames.Add(name); @@ -882,19 +892,25 @@ private OpenApiSchema GenerateSchemaFromFormParameters( if (ownSchemas.Count > 0) { - bool isAllOf = ownSchemas.Count > 1 || (ownSchemas.Count > 0 && properties.Count > 0); + bool isAllOf = + ownSchemas.Count > 1 || + (ownSchemas.Count > 0 && properties.Count > 0); + if (isAllOf) { var allOfSchema = new OpenApiSchema() { AllOf = ownSchemas }; + if (properties.Count > 0) { allOfSchema.AllOf.Add(GenerateSchemaForProperties(properties, requiredPropertyNames)); } + return allOfSchema; } + return ownSchemas.First(); } @@ -950,7 +966,8 @@ private OpenApiResponse GenerateResponse( private static IEnumerable InferResponseContentTypes(ApiDescription apiDescription, ApiResponseType apiResponseType) { // If there's no associated model type, return an empty list (i.e. no content) - if (apiResponseType.ModelMetadata == null && (apiResponseType.Type == null || apiResponseType.Type == typeof(void))) + if (apiResponseType.ModelMetadata == null && + (apiResponseType.Type == null || apiResponseType.Type == typeof(void))) { return []; } @@ -959,15 +976,16 @@ private static IEnumerable InferResponseContentTypes(ApiDescription apiD var explicitContentTypes = apiDescription.CustomAttributes().OfType() .SelectMany(attr => attr.ContentTypes) .Distinct(); - if (explicitContentTypes.Any()) return explicitContentTypes; + + if (explicitContentTypes.Any()) + { + return explicitContentTypes; + } // If there's content types surfaced by ApiExplorer, use them - var apiExplorerContentTypes = apiResponseType.ApiResponseFormats + return [.. apiResponseType.ApiResponseFormats .Select(responseFormat => responseFormat.MediaType) - .Distinct(); - if (apiExplorerContentTypes.Any()) return apiExplorerContentTypes; - - return []; + .Distinct()]; } private OpenApiMediaType CreateResponseMediaType(Type modelType, SchemaRepository schemaRespository) @@ -986,7 +1004,7 @@ private static bool IsFromFormAttributeUsedWithIFormFile(ApiParameterDescription return fromFormAttribute != null && parameterInfo?.ParameterType == typeof(IFormFile); } - private static readonly Dictionary OperationTypeMap = new() + private static readonly Dictionary OperationTypeMap = new(StringComparer.OrdinalIgnoreCase) { ["GET"] = OperationType.Get, ["PUT"] = OperationType.Put, diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorException.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorException.cs index ff7ec4b4ae..9a53529a85 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorException.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorException.cs @@ -2,11 +2,13 @@ public class SwaggerGeneratorException : Exception { - public SwaggerGeneratorException(string message) : base(message) + public SwaggerGeneratorException(string message) + : base(message) { } - public SwaggerGeneratorException(string message, Exception innerException) : base(message, innerException) + public SwaggerGeneratorException(string message, Exception innerException) + : base(message, innerException) { } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs index b6781edf59..b762ced1d7 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGeneratorOptions.cs @@ -3,7 +3,9 @@ #endif using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc.ApiExplorer; +#if NET using Microsoft.AspNetCore.Routing; +#endif using Microsoft.OpenApi.Models; namespace Swashbuckle.AspNetCore.SwaggerGen; @@ -20,17 +22,17 @@ public SwaggerGeneratorOptions() PathGroupSelector = DefaultPathGroupSelector; SecuritySchemesSelector = null; SchemaComparer = StringComparer.Ordinal; - Servers = new List(); + Servers = []; SecuritySchemes = new Dictionary(); - SecurityRequirements = new List(); - ParameterFilters = new List(); - ParameterAsyncFilters = new List(); - RequestBodyFilters = new List(); - RequestBodyAsyncFilters = new List(); - OperationFilters = new List(); - OperationAsyncFilters = new List(); - DocumentFilters = new List(); - DocumentAsyncFilters = new List(); + SecurityRequirements = []; + ParameterFilters = []; + ParameterAsyncFilters = []; + RequestBodyFilters = []; + RequestBodyAsyncFilters = []; + OperationFilters = []; + OperationAsyncFilters = []; + DocumentFilters = []; + DocumentAsyncFilters = []; } public IDictionary SwaggerDocs { get; set; } @@ -106,15 +108,15 @@ private string DefaultOperationIdSelector(ApiDescription apiDescription) private IList DefaultTagsSelector(ApiDescription apiDescription) { #if !NET - return new[] { apiDescription.ActionDescriptor.RouteValues["controller"] }; + return [apiDescription.ActionDescriptor.RouteValues["controller"]]; #else var actionDescriptor = apiDescription.ActionDescriptor; - var tagsMetadata = actionDescriptor.EndpointMetadata?.LastOrDefault(m => m is ITagsMetadata) as ITagsMetadata; - if (tagsMetadata != null) + if (actionDescriptor.EndpointMetadata?.LastOrDefault(m => m is ITagsMetadata) is ITagsMetadata metadata) { - return new List(tagsMetadata.Tags); + return [.. metadata.Tags]; } - return new[] { apiDescription.ActionDescriptor.RouteValues["controller"] }; + + return [apiDescription.ActionDescriptor.RouteValues["controller"]]; #endif } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/MethodInfoExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/MethodInfoExtensions.cs index ebc2a86e37..e5dadbaeb0 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/MethodInfoExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/MethodInfoExtensions.cs @@ -13,7 +13,8 @@ public static MethodInfo GetUnderlyingGenericTypeMethod(this MethodInfo construc var constructedTypeParameters = constructedTypeMethod.GetParameters(); // Retrieve list of candidate methods that match name and parameter count - var candidateMethods = genericTypeDefinition.GetMethods() + var candidateMethods = genericTypeDefinition + .GetMethods() .Where(m => { var genericTypeDefinitionParameters = m.GetParameters(); @@ -75,6 +76,6 @@ public static MethodInfo GetUnderlyingGenericTypeMethod(this MethodInfo construc // If inconclusive, just return null - return (candidateMethods.Count() == 1) ? candidateMethods.First() : null; + return candidateMethods.Count() == 1 ? candidateMethods.First() : null; } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsDocumentFilter.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsDocumentFilter.cs index 04a5fd168f..a7f5c17a13 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsDocumentFilter.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsDocumentFilter.cs @@ -16,7 +16,8 @@ public XmlCommentsDocumentFilter(XPathDocument xmlDoc) { } - public XmlCommentsDocumentFilter(XPathDocument xmlDoc, SwaggerGeneratorOptions options) : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc), options) + public XmlCommentsDocumentFilter(XPathDocument xmlDoc, SwaggerGeneratorOptions options) + : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc), options) { } @@ -47,7 +48,7 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) var summaryNode = typeNode.SelectFirstChild(SummaryTag); if (summaryNode != null) { - swaggerDoc.Tags ??= new List(); + swaggerDoc.Tags ??= []; swaggerDoc.Tags.Add(new OpenApiTag { diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsDocumentHelper.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsDocumentHelper.cs index 5fe84ab8d1..5a1ac4e3ff 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsDocumentHelper.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsDocumentHelper.cs @@ -12,11 +12,6 @@ internal static Dictionary CreateMemberDictionary(XPathD ?.SelectChildren("member") ?.OfType(); - if (members == null) - { - return new Dictionary(); - } - - return members.ToDictionary(memberNode => memberNode.GetAttribute("name")); + return members?.ToDictionary(memberNode => memberNode.GetAttribute("name")) ?? []; } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsNodeNameHelper.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsNodeNameHelper.cs index 7d5886e747..82a256a974 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsNodeNameHelper.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsNodeNameHelper.cs @@ -13,7 +13,7 @@ public static string GetMemberNameForMethod(MethodInfo method) builder.Append($".{method.Name}"); var parameters = method.GetParameters(); - if (parameters.Any()) + if (parameters.Length != 0) { var parametersNames = parameters.Select(p => { @@ -56,7 +56,9 @@ private static string QualifiedNameFor(Type type, bool expandGenericArgs = false var builder = new StringBuilder(); if (!string.IsNullOrEmpty(type.Namespace)) + { builder.Append($"{type.Namespace}."); + } if (type.IsNested) { @@ -87,7 +89,10 @@ private static string QualifiedNameFor(Type type, bool expandGenericArgs = false private static IEnumerable GetNestedTypeNames(Type type) { - if (!type.IsNested || type.DeclaringType == null) yield break; + if (!type.IsNested || type.DeclaringType == null) + { + yield break; + } foreach (var nestedTypeName in GetNestedTypeNames(type.DeclaringType)) { diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsOperationFilter.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsOperationFilter.cs index 2e3a160026..4db7b72636 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsOperationFilter.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsOperationFilter.cs @@ -1,7 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; -using System.Reflection; +using System.Reflection; using System.Xml.XPath; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; namespace Swashbuckle.AspNetCore.SwaggerGen; @@ -10,7 +10,8 @@ public class XmlCommentsOperationFilter : IOperationFilter private readonly IReadOnlyDictionary _xmlDocMembers; private readonly SwaggerGeneratorOptions _options; - public XmlCommentsOperationFilter(XPathDocument xmlDoc) : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc), null) + public XmlCommentsOperationFilter(XPathDocument xmlDoc) + : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc), null) { } @@ -23,42 +24,54 @@ internal XmlCommentsOperationFilter(IReadOnlyDictionary public void Apply(OpenApiOperation operation, OperationFilterContext context) { - if (context.MethodInfo == null) return; + if (context.MethodInfo == null) + { + return; + } // If method is from a constructed generic type, look for comments from the generic type method var targetMethod = context.MethodInfo.DeclaringType.IsConstructedGenericType ? context.MethodInfo.GetUnderlyingGenericTypeMethod() : context.MethodInfo; - if (targetMethod == null) return; - - ApplyControllerTags(operation, targetMethod.DeclaringType); - ApplyMethodTags(operation, targetMethod); + if (targetMethod != null) + { + ApplyControllerTags(operation, targetMethod.DeclaringType); + ApplyMethodTags(operation, targetMethod); + } } private void ApplyControllerTags(OpenApiOperation operation, Type controllerType) { var typeMemberName = XmlCommentsNodeNameHelper.GetMemberNameForType(controllerType); - if (!_xmlDocMembers.TryGetValue(typeMemberName, out var methodNode)) return; - - var responseNodes = methodNode.SelectChildren("response"); - ApplyResponseTags(operation, responseNodes); + if (_xmlDocMembers.TryGetValue(typeMemberName, out var methodNode)) + { + var responseNodes = methodNode.SelectChildren("response"); + ApplyResponseTags(operation, responseNodes); + } } private void ApplyMethodTags(OpenApiOperation operation, MethodInfo methodInfo) { var methodMemberName = XmlCommentsNodeNameHelper.GetMemberNameForMethod(methodInfo); - if (!_xmlDocMembers.TryGetValue(methodMemberName, out var methodNode)) return; + if (!_xmlDocMembers.TryGetValue(methodMemberName, out var methodNode)) + { + return; + } var summaryNode = methodNode.SelectFirstChild("summary"); if (summaryNode != null) + { operation.Summary = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml, _options?.XmlCommentEndOfLine); + } var remarksNode = methodNode.SelectFirstChild("remarks"); if (remarksNode != null) + { operation.Description = XmlCommentsTextHelper.Humanize(remarksNode.InnerXml, _options?.XmlCommentEndOfLine); + } var responseNodes = methodNode.SelectChildren("response"); ApplyResponseTags(operation, responseNodes); diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsParameterFilter.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsParameterFilter.cs index 1116b84cab..8139eae9dc 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsParameterFilter.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsParameterFilter.cs @@ -1,7 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; -using System.Reflection; +using System.Reflection; using System.Xml.XPath; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; namespace Swashbuckle.AspNetCore.SwaggerGen; @@ -10,7 +10,8 @@ public class XmlCommentsParameterFilter : IParameterFilter private readonly IReadOnlyDictionary _xmlDocMembers; private readonly SwaggerGeneratorOptions _options; - public XmlCommentsParameterFilter(XPathDocument xmlDoc) : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc), null) + public XmlCommentsParameterFilter(XPathDocument xmlDoc) + : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc), null) { } @@ -43,18 +44,22 @@ private void ApplyPropertyTags(OpenApiParameter parameter, ParameterFilterContex if (summaryNode != null) { parameter.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml, _options?.XmlCommentEndOfLine); - parameter.Schema.Description = null; // no need to duplicate + parameter.Schema.Description = null; // No need to duplicate } var exampleNode = propertyNode.SelectFirstChild("example"); - if (exampleNode == null) return; - - parameter.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, parameter.Schema, exampleNode.ToString()); + if (exampleNode != null) + { + parameter.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, parameter.Schema, exampleNode.ToString()); + } } private void ApplyParamTags(OpenApiParameter parameter, ParameterFilterContext context) { - if (!(context.ParameterInfo.Member is MethodInfo methodInfo)) return; + if (context.ParameterInfo.Member is not MethodInfo methodInfo) + { + return; + } // If method is from a constructed generic type, look for comments from the generic type method var targetMethod = methodInfo.DeclaringType.IsConstructedGenericType @@ -65,7 +70,10 @@ private void ApplyParamTags(OpenApiParameter parameter, ParameterFilterContext c var methodMemberName = XmlCommentsNodeNameHelper.GetMemberNameForMethod(targetMethod); - if (!_xmlDocMembers.TryGetValue(methodMemberName, out var propertyNode)) return; + if (!_xmlDocMembers.TryGetValue(methodMemberName, out var propertyNode)) + { + return; + } XPathNavigator paramNode = propertyNode.SelectFirstChildWithAttribute("param", "name", context.ParameterInfo.Name); @@ -74,9 +82,10 @@ private void ApplyParamTags(OpenApiParameter parameter, ParameterFilterContext c parameter.Description = XmlCommentsTextHelper.Humanize(paramNode.InnerXml, _options?.XmlCommentEndOfLine); var example = paramNode.GetAttribute("example"); - if (string.IsNullOrEmpty(example)) return; - - parameter.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, parameter.Schema, example); + if (!string.IsNullOrEmpty(example)) + { + parameter.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, parameter.Schema, example); + } } } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsRequestBodyFilter.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsRequestBodyFilter.cs index 7318972bb7..ea15f3e128 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsRequestBodyFilter.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsRequestBodyFilter.cs @@ -10,7 +10,8 @@ public class XmlCommentsRequestBodyFilter : IRequestBodyFilter private readonly IReadOnlyDictionary _xmlDocMembers; private readonly SwaggerGeneratorOptions _options; - public XmlCommentsRequestBodyFilter(XPathDocument xmlDoc) : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc), null) + public XmlCommentsRequestBodyFilter(XPathDocument xmlDoc) + : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc), null) { } @@ -64,8 +65,8 @@ public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext conte foreach (var item in requestBody.Content.Values) { - if (item?.Schema?.Properties is { } properties - && (properties.TryGetValue(formParameter.Name, out var value) || properties.TryGetValue(formParameter.Name.ToCamelCase(), out value))) + if (item?.Schema?.Properties is { } properties && + (properties.TryGetValue(formParameter.Name, out var value) || properties.TryGetValue(formParameter.Name.ToCamelCase(), out value))) { var (summary, example) = GetParamTags(parameterFromForm); value.Description ??= summary; diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsSchemaFilter.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsSchemaFilter.cs index ee122c1ec5..b564444df5 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsSchemaFilter.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsSchemaFilter.cs @@ -9,7 +9,8 @@ public class XmlCommentsSchemaFilter : ISchemaFilter private readonly IReadOnlyDictionary _xmlDocMembers; private readonly SwaggerGeneratorOptions _options; - public XmlCommentsSchemaFilter(XPathDocument xmlDoc) : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc), null) + public XmlCommentsSchemaFilter(XPathDocument xmlDoc) + : this(XmlCommentsDocumentHelper.CreateMemberDictionary(xmlDoc), null) { } @@ -85,9 +86,9 @@ private void ApplyMemberTags(OpenApiSchema schema, SchemaFilterContext context) private static void TrySetExample(OpenApiSchema schema, SchemaFilterContext context, string example) { - if (example == null) - return; - - schema.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, schema, example); + if (example != null) + { + schema.Example = XmlCommentsExampleHelper.Create(context.SchemaRepository, schema, example); + } } } diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsTextHelper.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsTextHelper.cs index 1a29cce6ca..31ddf384d3 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsTextHelper.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/XmlComments/XmlCommentsTextHelper.cs @@ -14,9 +14,11 @@ public static string Humanize(string text) public static string Humanize(string text, string xmlCommentEndOfLine) { if (text == null) + { throw new ArgumentNullException(nameof(text)); + } - //Call DecodeXml at last to avoid entities like < and > to break valid xml + // Call DecodeXml last to avoid entities like < and > breaking valid XML return text .NormalizeIndentation(xmlCommentEndOfLine) @@ -25,7 +27,7 @@ public static string Humanize(string text, string xmlCommentEndOfLine) .HumanizeCodeTags() .HumanizeMultilineCodeTags(xmlCommentEndOfLine) .HumanizeParaTags() - .HumanizeBrTags(xmlCommentEndOfLine) // must be called after HumanizeParaTags() so that it replaces any additional
tags + .HumanizeBrTags(xmlCommentEndOfLine) // Must be called after HumanizeParaTags() so that it replaces any additional
tags .DecodeXml(); } @@ -36,54 +38,77 @@ private static string NormalizeIndentation(this string text, string xmlCommentEn int padLen = padding?.Length ?? 0; - // remove leading padding from each line + // Remove leading padding from each line for (int i = 0, l = lines.Length; i < l; ++i) { - string line = lines[i].TrimEnd('\r'); // remove trailing '\r' + string line = lines[i].TrimEnd('\r'); // Remove trailing '\r' +#if NET + if (padLen != 0 && line.Length >= padLen && line[..padLen] == padding) + { + line = line[padLen..]; + } +#else if (padLen != 0 && line.Length >= padLen && line.Substring(0, padLen) == padding) + { line = line.Substring(padLen); + } +#endif lines[i] = line; } - // remove leading empty lines, but not all leading padding - // remove all trailing whitespace, regardless + // Remove leading empty lines, but not all leading padding + // Remove all trailing whitespace, regardless return string.Join(EndOfLine(xmlCommentEndOfLine), lines.SkipWhile(string.IsNullOrWhiteSpace)).TrimEnd(); } private static string GetCommonLeadingWhitespace(string[] lines) { if (null == lines) + { throw new ArgumentException("lines"); + } if (lines.Length == 0) + { return null; + } - string[] nonEmptyLines = lines - .Where(x => !string.IsNullOrWhiteSpace(x)) - .ToArray(); + string[] nonEmptyLines = [.. lines.Where(x => !string.IsNullOrWhiteSpace(x))]; if (nonEmptyLines.Length < 1) + { return null; + } - int padLen = 0; + int padLength = 0; - // use the first line as a seed, and see what is shared over all nonEmptyLines + // Use the first line as a seed, and see what is shared over all nonEmptyLines string seed = nonEmptyLines[0]; for (int i = 0, l = seed.Length; i < l; ++i) { if (!char.IsWhiteSpace(seed, i)) + { break; + } if (nonEmptyLines.Any(line => line[i] != seed[i])) + { break; + } - ++padLen; + ++padLength; } - if (padLen > 0) - return seed.Substring(0, padLen); + if (padLength > 0) + { +#if NET + return seed[..padLength]; +#else + return seed.Substring(0, padLength); +#endif + } return null; } @@ -108,16 +133,27 @@ private static string HumanizeMultilineCodeTags(this string text, string xmlComm return MultilineCodeTag().Replace(text, match => { var codeText = match.Groups["display"].Value; + if (LineBreaks().IsMatch(codeText)) { var builder = new StringBuilder().Append("```"); + +#if NET + if (!codeText.StartsWith('\r') && !codeText.StartsWith('\n')) +#else if (!codeText.StartsWith("\r") && !codeText.StartsWith("\n")) +#endif { builder.Append(EndOfLine(xmlCommentEndOfLine)); } builder.Append(RemoveCommonLeadingWhitespace(codeText, xmlCommentEndOfLine)); + +#if NET + if (!codeText.EndsWith('\n')) +#else if (!codeText.EndsWith("\n")) +#endif { builder.Append(EndOfLine(xmlCommentEndOfLine)); } @@ -149,6 +185,7 @@ private static string RemoveCommonLeadingWhitespace(string input, string xmlComm { var lines = input.Split(["\r\n", "\n"], StringSplitOptions.None); var padding = GetCommonLeadingWhitespace(lines); + if (string.IsNullOrEmpty(padding)) { return input; @@ -156,11 +193,17 @@ private static string RemoveCommonLeadingWhitespace(string input, string xmlComm var minLeadingSpaces = padding.Length; var builder = new StringBuilder(); + foreach (var line in lines) { builder.Append(string.IsNullOrWhiteSpace(line) ? line +#if NET + : line[minLeadingSpaces..]); +#else : line.Substring(minLeadingSpaces)); +#endif + builder.Append(EndOfLine(xmlCommentEndOfLine)); } diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/ConfigObject.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/ConfigObject.cs new file mode 100644 index 0000000000..e8015e1836 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/ConfigObject.cs @@ -0,0 +1,138 @@ +using System.Text.Json.Serialization; + +namespace Swashbuckle.AspNetCore.SwaggerUI; + +public class ConfigObject +{ + /// + /// One or more Swagger JSON endpoints (url and name) to power the UI + /// + [JsonPropertyName("urls")] + public IEnumerable Urls { get; set; } + + /// + /// If set to true, enables deep linking for tags and operations + /// + [JsonPropertyName("deepLinking")] + public bool DeepLinking { get; set; } + + /// + /// If set to true, it persists authorization data and it would not be lost on browser close/refresh + /// + [JsonPropertyName("persistAuthorization")] + public bool PersistAuthorization { get; set; } + + /// + /// Controls the display of operationId in operations list + /// + [JsonPropertyName("displayOperationId")] + public bool DisplayOperationId { get; set; } + + /// + /// The default expansion depth for models (set to -1 completely hide the models) + /// + [JsonPropertyName("defaultModelsExpandDepth")] + public int DefaultModelsExpandDepth { get; set; } = 1; + + /// + /// The default expansion depth for the model on the model-example section + /// + [JsonPropertyName("defaultModelExpandDepth")] + public int DefaultModelExpandDepth { get; set; } = 1; + + /// + /// Controls how the model is shown when the API is first rendered. + /// (The user can always switch the rendering for a given model by clicking the 'Model' and 'Example Value' links) + /// +#if NET + [JsonConverter(typeof(JavascriptStringEnumConverter))] +#endif + [JsonPropertyName("defaultModelRendering")] + public ModelRendering DefaultModelRendering { get; set; } = ModelRendering.Example; + + /// + /// Controls the display of the request duration (in milliseconds) for Try-It-Out requests + /// + [JsonPropertyName("displayRequestDuration")] + public bool DisplayRequestDuration { get; set; } + + /// + /// Controls the default expansion setting for the operations and tags. + /// It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing) + /// +#if NET + [JsonConverter(typeof(JavascriptStringEnumConverter))] +#endif + [JsonPropertyName("docExpansion")] + public DocExpansion DocExpansion { get; set; } = DocExpansion.List; + + /// + /// If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations + /// that are shown. Can be an empty string or specific value, in which case filtering will be enabled using that + /// value as the filter expression. Filtering is case sensitive matching the filter expression anywhere inside the tag + /// + [JsonPropertyName("filter")] + public string Filter { get; set; } + + /// + /// If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations + /// + [JsonPropertyName("maxDisplayedTags")] + public int? MaxDisplayedTags { get; set; } + + /// + /// Controls the display of vendor extension (x-) fields and values for Operations, Parameters, and Schema + /// + [JsonPropertyName("showExtensions")] + public bool ShowExtensions { get; set; } + + /// + /// Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields and values for Parameters + /// + [JsonPropertyName("showCommonExtensions")] + public bool ShowCommonExtensions { get; set; } + + /// + /// OAuth redirect URL + /// + [JsonPropertyName("oauth2RedirectUrl")] + public string OAuth2RedirectUrl { get; set; } + + /// + /// List of HTTP methods that have the Try it out feature enabled. + /// An empty array disables Try it out for all operations. This does not filter the operations from the display + /// +#if NET + [JsonConverter(typeof(JavascriptStringEnumEnumerableConverter))] +#endif + [JsonPropertyName("supportedSubmitMethods")] + public IEnumerable SupportedSubmitMethods { get; set; } = +#if NET + Enum.GetValues(); +#else + Enum.GetValues(typeof(SubmitMethod)).Cast(); +#endif + + /// + /// Controls whether the "Try it out" section should be enabled by default. + /// + [JsonPropertyName("tryItOutEnabled")] + public bool TryItOutEnabled { get; set; } + + /// + /// By default, Swagger-UI attempts to validate specs against swagger.io's online validator. + /// You can use this parameter to set a different validator URL, for example for locally deployed validators (Validator Badge). + /// Setting it to null will disable validation + /// + [JsonPropertyName("validatorUrl")] + public string ValidatorUrl { get; set; } + + /// + /// Any custom plugins' function names. + /// + [JsonPropertyName("plugins")] + public IList Plugins { get; set; } + + [JsonExtensionData] + public Dictionary AdditionalItems { get; set; } = []; +} diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/DocExpansion.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/DocExpansion.cs new file mode 100644 index 0000000000..4ee0836df1 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/DocExpansion.cs @@ -0,0 +1,8 @@ +namespace Swashbuckle.AspNetCore.SwaggerUI; + +public enum DocExpansion +{ + List, + Full, + None, +} diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/InterceptorFunctions.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/InterceptorFunctions.cs new file mode 100644 index 0000000000..f248faa4a9 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/InterceptorFunctions.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace Swashbuckle.AspNetCore.SwaggerUI; + +public class InterceptorFunctions +{ + /// + /// MUST be a valid Javascript function. + /// Function to intercept remote definition, "Try it out", and OAuth 2.0 requests. + /// Accepts one argument requestInterceptor(request) and must return the modified request, or a Promise that resolves to the modified request. + /// Ex: "function (req) { req.headers['MyCustomHeader'] = 'CustomValue'; return req; }" + /// + [JsonPropertyName("RequestInterceptorFunction")] + public string RequestInterceptorFunction { get; set; } + + /// + /// MUST be a valid Javascript function. + /// Function to intercept remote definition, "Try it out", and OAuth 2.0 responses. + /// Accepts one argument responseInterceptor(response) and must return the modified response, or a Promise that resolves to the modified response. + /// Ex: "function (res) { console.log(res); return res; }" + /// + [JsonPropertyName("ResponseInterceptorFunction")] + public string ResponseInterceptorFunction { get; set; } +} diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/ModelRendering.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/ModelRendering.cs new file mode 100644 index 0000000000..7b44b62755 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/ModelRendering.cs @@ -0,0 +1,7 @@ +namespace Swashbuckle.AspNetCore.SwaggerUI; + +public enum ModelRendering +{ + Example, + Model, +} diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/OAuthConfigObject.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/OAuthConfigObject.cs new file mode 100644 index 0000000000..5eb6e18c43 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/OAuthConfigObject.cs @@ -0,0 +1,69 @@ +using System.Text.Json.Serialization; + +namespace Swashbuckle.AspNetCore.SwaggerUI; + +public class OAuthConfigObject +{ + /// + /// Default username for OAuth2 password flow. + /// + public string Username { get; set; } + + /// + /// Default clientId + /// + [JsonPropertyName("clientId")] + public string ClientId { get; set; } + + /// + /// Default clientSecret + /// + /// Setting this exposes the client secrets in inline javascript in the swagger-ui generated html. + [JsonPropertyName("clientSecret")] + public string ClientSecret { get; set; } + + /// + /// Realm query parameter (for oauth1) added to authorizationUrl and tokenUrl + /// + [JsonPropertyName("realm")] + public string Realm { get; set; } + + /// + /// Application name, displayed in authorization popup + /// + [JsonPropertyName("appName")] + public string AppName { get; set; } + + /// + /// Scope separator for passing scopes, encoded before calling, default value is a space (encoded value %20) + /// + [JsonPropertyName("scopeSeparator")] + public string ScopeSeparator { get; set; } = " "; + + /// + /// String array of initially selected oauth scopes, default is empty array + /// + [JsonPropertyName("scopes")] + public IEnumerable Scopes { get; set; } = []; + + /// + /// Additional query parameters added to authorizationUrl and tokenUrl + /// + [JsonPropertyName("additionalQueryStringParams")] + public Dictionary AdditionalQueryStringParams { get; set; } + + /// + /// Only activated for the accessCode flow. During the authorization_code request to the tokenUrl, + /// pass the Client Password using the HTTP Basic Authentication scheme + /// (Authorization header with Basic base64encode(client_id + client_secret)) + /// + [JsonPropertyName("useBasicAuthenticationWithAccessCodeGrant")] + public bool UseBasicAuthenticationWithAccessCodeGrant { get; set; } + + /// + /// Only applies to authorizatonCode flows. Proof Key for Code Exchange brings enhanced security for OAuth public clients. + /// The default is false + /// + [JsonPropertyName("usePkceWithAuthorizationCodeGrant")] + public bool UsePkceWithAuthorizationCodeGrant { get; set; } +} diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/SubmitMethod.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/SubmitMethod.cs new file mode 100644 index 0000000000..4dc22b7352 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/SubmitMethod.cs @@ -0,0 +1,13 @@ +namespace Swashbuckle.AspNetCore.SwaggerUI; + +public enum SubmitMethod +{ + Get, + Put, + Post, + Delete, + Options, + Head, + Patch, + Trace, +} diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIBuilderExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIBuilderExtensions.cs index b7db72cb72..15616940b4 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIBuilderExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIBuilderExtensions.cs @@ -1,9 +1,10 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Swashbuckle.AspNetCore.SwaggerUI; -#if !NET +#if NET +using Microsoft.AspNetCore.Hosting; +#else using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; #endif diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs index d25240304f..c00b85cfe7 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIMiddleware.cs @@ -65,7 +65,12 @@ public async Task Invoke(HttpContext httpContext) if (Regex.IsMatch(path, $"^/?{Regex.Escape(_options.RoutePrefix)}/?$", RegexOptions.IgnoreCase)) { // Use relative redirect to support proxy environments - var relativeIndexUrl = string.IsNullOrEmpty(path) || path.EndsWith("/") + var relativeIndexUrl = +#if NET + string.IsNullOrEmpty(path) || path.EndsWith('/') +#else + string.IsNullOrEmpty(path) || path.EndsWith("/") +#endif ? "index.html" : $"{path.Split('/').Last()}/index.html"; diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs index ca2372530d..9ece8ca48b 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptions.cs @@ -1,5 +1,4 @@ using System.Text.Json; -using System.Text.Json.Serialization; namespace Swashbuckle.AspNetCore.SwaggerUI; @@ -13,7 +12,7 @@ public class SwaggerUIOptions /// /// Gets or sets a Stream function for retrieving the swagger-ui page /// - public Func IndexStream { get; set; } = () => ResourceHelper.GetEmbeddedResource("index.html"); + public Func IndexStream { get; set; } = static () => ResourceHelper.GetEmbeddedResource("index.html"); /// /// Gets or sets a title for the swagger-ui page @@ -28,17 +27,17 @@ public class SwaggerUIOptions /// /// Gets the JavaScript config object, represented as JSON, that will be passed to the SwaggerUI /// - public ConfigObject ConfigObject { get; set; } = new ConfigObject(); + public ConfigObject ConfigObject { get; set; } = new(); /// /// Gets the JavaScript config object, represented as JSON, that will be passed to the initOAuth method /// - public OAuthConfigObject OAuthConfigObject { get; set; } = new OAuthConfigObject(); + public OAuthConfigObject OAuthConfigObject { get; set; } = new(); /// /// Gets the interceptor functions that define client-side request/response interceptors /// - public InterceptorFunctions Interceptors { get; set; } = new InterceptorFunctions(); + public InterceptorFunctions Interceptors { get; set; } = new(); /// /// Gets or sets the optional JSON serialization options to use to serialize options to the HTML document. @@ -61,270 +60,14 @@ public class SwaggerUIOptions public string StylesPath { get; set; } = "./swagger-ui.css"; /// - /// Gets or sets whether to expose the ConfigObject.Urls object via an + /// Gets or sets whether to expose the ConfigObject.Urls object via an /// HTTP endpoint with the URL specified by /// so that external code can auto-discover all Swagger documents. /// - public bool ExposeSwaggerDocumentUrlsRoute { get; set; } = false; + public bool ExposeSwaggerDocumentUrlsRoute { get; set; } /// /// Gets or sets the relative URL path to the route that exposes the values of the configured values. /// public string SwaggerDocumentUrlsPath { get; set; } = "documentUrls"; } - -public class ConfigObject -{ - /// - /// One or more Swagger JSON endpoints (url and name) to power the UI - /// - [JsonPropertyName("urls")] - public IEnumerable Urls { get; set; } = null; - - /// - /// If set to true, enables deep linking for tags and operations - /// - [JsonPropertyName("deepLinking")] - public bool DeepLinking { get; set; } = false; - - /// - /// If set to true, it persists authorization data and it would not be lost on browser close/refresh - /// - [JsonPropertyName("persistAuthorization")] - public bool PersistAuthorization { get; set; } = false; - - /// - /// Controls the display of operationId in operations list - /// - [JsonPropertyName("displayOperationId")] - public bool DisplayOperationId { get; set; } = false; - - /// - /// The default expansion depth for models (set to -1 completely hide the models) - /// - [JsonPropertyName("defaultModelsExpandDepth")] - public int DefaultModelsExpandDepth { get; set; } = 1; - - /// - /// The default expansion depth for the model on the model-example section - /// - [JsonPropertyName("defaultModelExpandDepth")] - public int DefaultModelExpandDepth { get; set; } = 1; - - /// - /// Controls how the model is shown when the API is first rendered. - /// (The user can always switch the rendering for a given model by clicking the 'Model' and 'Example Value' links) - /// -#if NET - [JsonConverter(typeof(JavascriptStringEnumConverter))] -#endif - [JsonPropertyName("defaultModelRendering")] - public ModelRendering DefaultModelRendering { get; set; } = ModelRendering.Example; - - /// - /// Controls the display of the request duration (in milliseconds) for Try-It-Out requests - /// - [JsonPropertyName("displayRequestDuration")] - public bool DisplayRequestDuration { get; set; } = false; - - /// - /// Controls the default expansion setting for the operations and tags. - /// It can be 'list' (expands only the tags), 'full' (expands the tags and operations) or 'none' (expands nothing) - /// -#if NET - [JsonConverter(typeof(JavascriptStringEnumConverter))] -#endif - [JsonPropertyName("docExpansion")] - public DocExpansion DocExpansion { get; set; } = DocExpansion.List; - - /// - /// If set, enables filtering. The top bar will show an edit box that you can use to filter the tagged operations - /// that are shown. Can be an empty string or specific value, in which case filtering will be enabled using that - /// value as the filter expression. Filtering is case sensitive matching the filter expression anywhere inside the tag - /// - [JsonPropertyName("filter")] - public string Filter { get; set; } = null; - - /// - /// If set, limits the number of tagged operations displayed to at most this many. The default is to show all operations - /// - [JsonPropertyName("maxDisplayedTags")] - public int? MaxDisplayedTags { get; set; } = null; - - /// - /// Controls the display of vendor extension (x-) fields and values for Operations, Parameters, and Schema - /// - [JsonPropertyName("showExtensions")] - public bool ShowExtensions { get; set; } = false; - - /// - /// Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields and values for Parameters - /// - [JsonPropertyName("showCommonExtensions")] - public bool ShowCommonExtensions { get; set; } = false; - - /// - /// OAuth redirect URL - /// - [JsonPropertyName("oauth2RedirectUrl")] - public string OAuth2RedirectUrl { get; set; } = null; - - /// - /// List of HTTP methods that have the Try it out feature enabled. - /// An empty array disables Try it out for all operations. This does not filter the operations from the display - /// -#if NET - [JsonConverter(typeof(JavascriptStringEnumEnumerableConverter))] -#endif - [JsonPropertyName("supportedSubmitMethods")] - public IEnumerable SupportedSubmitMethods { get; set; } = -#if NET - Enum.GetValues(); -#else - Enum.GetValues(typeof(SubmitMethod)).Cast(); -#endif - - /// - /// Controls whether the "Try it out" section should be enabled by default. - /// - [JsonPropertyName("tryItOutEnabled")] - public bool TryItOutEnabled { get; set; } - - /// - /// By default, Swagger-UI attempts to validate specs against swagger.io's online validator. - /// You can use this parameter to set a different validator URL, for example for locally deployed validators (Validator Badge). - /// Setting it to null will disable validation - /// - [JsonPropertyName("validatorUrl")] - public string ValidatorUrl { get; set; } = null; - - /// - /// Any custom plugins' function names. - /// - [JsonPropertyName("plugins")] - public IList Plugins { get; set; } = null; - - [JsonExtensionData] - public Dictionary AdditionalItems { get; set; } = []; -} - -public class UrlDescriptor -{ - [JsonPropertyName("url")] - public string Url { get; set; } - - [JsonPropertyName("name")] - public string Name { get; set; } -} - -public enum ModelRendering -{ - Example, - Model -} - -public enum DocExpansion -{ - List, - Full, - None -} - -public enum SubmitMethod -{ - Get, - Put, - Post, - Delete, - Options, - Head, - Patch, - Trace -} - -public class OAuthConfigObject -{ - /// - /// Default username for OAuth2 password flow. - /// - public string Username { get; set; } = null; - - /// - /// Default clientId - /// - [JsonPropertyName("clientId")] - public string ClientId { get; set; } = null; - - /// - /// Default clientSecret - /// - /// Setting this exposes the client secrets in inline javascript in the swagger-ui generated html. - [JsonPropertyName("clientSecret")] - public string ClientSecret { get; set; } = null; - - /// - /// Realm query parameter (for oauth1) added to authorizationUrl and tokenUrl - /// - [JsonPropertyName("realm")] - public string Realm { get; set; } = null; - - /// - /// Application name, displayed in authorization popup - /// - [JsonPropertyName("appName")] - public string AppName { get; set; } = null; - - /// - /// Scope separator for passing scopes, encoded before calling, default value is a space (encoded value %20) - /// - [JsonPropertyName("scopeSeparator")] - public string ScopeSeparator { get; set; } = " "; - - /// - /// String array of initially selected oauth scopes, default is empty array - /// - [JsonPropertyName("scopes")] - public IEnumerable Scopes { get; set; } = []; - - /// - /// Additional query parameters added to authorizationUrl and tokenUrl - /// - [JsonPropertyName("additionalQueryStringParams")] - public Dictionary AdditionalQueryStringParams { get; set; } = null; - - /// - /// Only activated for the accessCode flow. During the authorization_code request to the tokenUrl, - /// pass the Client Password using the HTTP Basic Authentication scheme - /// (Authorization header with Basic base64encode(client_id + client_secret)) - /// - [JsonPropertyName("useBasicAuthenticationWithAccessCodeGrant")] - public bool UseBasicAuthenticationWithAccessCodeGrant { get; set; } = false; - - /// - /// Only applies to authorizatonCode flows. Proof Key for Code Exchange brings enhanced security for OAuth public clients. - /// The default is false - /// - [JsonPropertyName("usePkceWithAuthorizationCodeGrant")] - public bool UsePkceWithAuthorizationCodeGrant { get; set; } = false; -} - -public class InterceptorFunctions -{ - /// - /// MUST be a valid Javascript function. - /// Function to intercept remote definition, "Try it out", and OAuth 2.0 requests. - /// Accepts one argument requestInterceptor(request) and must return the modified request, or a Promise that resolves to the modified request. - /// Ex: "function (req) { req.headers['MyCustomHeader'] = 'CustomValue'; return req; }" - /// - [JsonPropertyName("RequestInterceptorFunction")] - public string RequestInterceptorFunction { get; set; } - - /// - /// MUST be a valid Javascript function. - /// Function to intercept remote definition, "Try it out", and OAuth 2.0 responses. - /// Accepts one argument responseInterceptor(response) and must return the modified response, or a Promise that resolves to the modified response. - /// Ex: "function (res) { console.log(res); return res; }" - /// - [JsonPropertyName("ResponseInterceptorFunction")] - public string ResponseInterceptorFunction { get; set; } -} diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptionsExtensions.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptionsExtensions.cs index f88c5d36e6..d155d7b3f2 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptionsExtensions.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/SwaggerUIOptionsExtensions.cs @@ -54,6 +54,7 @@ public static void EnableDeepLinking(this SwaggerUIOptions options) { options.ConfigObject.DeepLinking = true; } + /// /// Enables persist authorization data /// diff --git a/src/Swashbuckle.AspNetCore.SwaggerUI/UrlDescriptor.cs b/src/Swashbuckle.AspNetCore.SwaggerUI/UrlDescriptor.cs new file mode 100644 index 0000000000..26f532f2b2 --- /dev/null +++ b/src/Swashbuckle.AspNetCore.SwaggerUI/UrlDescriptor.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace Swashbuckle.AspNetCore.SwaggerUI; + +public class UrlDescriptor +{ + [JsonPropertyName("url")] + public string Url { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } +}