diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs index bd31295eec..a84a640093 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs @@ -407,7 +407,7 @@ private OpenApiOperation GenerateOpenApiOperationFromMetadata(ApiDescription api if (apiParameter is not null) { parameter.Schema = GenerateSchema( - apiParameter.ModelMetadata.ModelType, + apiParameter.Type, schemaRepository, apiParameter.PropertyInfo(), apiParameter.ParameterInfo(), @@ -568,9 +568,19 @@ private OpenApiParameter GenerateParameterWithoutFilter( var isRequired = apiParameter.IsRequiredParameter(); - var schema = (apiParameter.ModelMetadata != null) + var type = apiParameter.Type; + + if (type is not null + && type == typeof(string) + && apiParameter.ModelMetadata?.ModelType is not null + && apiParameter.ModelMetadata.ModelType != type) + { + type = apiParameter.ModelMetadata.ModelType; + } + + var schema = (type != null) ? GenerateSchema( - apiParameter.ModelMetadata.ModelType, + type, schemaRepository, apiParameter.PropertyInfo(), apiParameter.ParameterInfo(), diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_For_WebApi_swaggerRequestUri=v1.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_For_WebApi_swaggerRequestUri=v1.verified.txt index 79c1b95f9a..7349ea852c 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_For_WebApi_swaggerRequestUri=v1.verified.txt +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.SwaggerEndpoint_ReturnsValidSwaggerJson_For_WebApi_swaggerRequestUri=v1.verified.txt @@ -48,6 +48,128 @@ } } }, + "/annotations/AsParameters": { + "get": { + "tags": [ + "Annotations" + ], + "parameters": [ + { + "name": "paramOne", + "in": "query", + "description": "Description", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "paramTwo", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "paramThree", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "paramFour", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "paramFive", + "in": "query", + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "paramSix", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "paramSeven", + "in": "query", + "schema": { + "type": "string", + "format": "time" + } + }, + { + "name": "paramEight", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "time" + } + }, + { + "name": "paramNine", + "in": "query", + "schema": { + "$ref": "#/components/schemas/DateTimeKind" + } + }, + { + "name": "paramTen", + "in": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/DateTimeKind" + } + }, + { + "name": "paramEleven", + "in": "query", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "paramTwelve", + "in": "query", + "required": true, + "schema": { + "type": "number", + "format": "double" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AsParametersRecord" + } + } + } + } + } + } + }, "/WithOpenApi/weatherforecast": { "get": { "tags": [ @@ -266,6 +388,72 @@ }, "additionalProperties": false }, + "AsParametersRecord": { + "type": "object", + "properties": { + "paramOne": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "paramTwo": { + "type": "string", + "format": "uuid" + }, + "paramThree": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "paramFour": { + "type": "string", + "format": "date-time" + }, + "paramFive": { + "type": "string", + "format": "date", + "nullable": true + }, + "paramSix": { + "type": "string", + "format": "date" + }, + "paramSeven": { + "type": "string", + "format": "time", + "nullable": true + }, + "paramEight": { + "type": "string", + "format": "time" + }, + "paramNine": { + "$ref": "#/components/schemas/DateTimeKind" + }, + "paramTen": { + "$ref": "#/components/schemas/DateTimeKind" + }, + "paramEleven": { + "type": "number", + "format": "double", + "nullable": true + }, + "paramTwelve": { + "type": "number", + "format": "double" + } + }, + "additionalProperties": false + }, + "DateTimeKind": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer", + "format": "int32" + }, "Fruit": { "type": "object", "properties": { diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.TypesAreRenderedCorrectly.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.TypesAreRenderedCorrectly.verified.txt index 79c1b95f9a..7349ea852c 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.TypesAreRenderedCorrectly.verified.txt +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.TypesAreRenderedCorrectly.verified.txt @@ -48,6 +48,128 @@ } } }, + "/annotations/AsParameters": { + "get": { + "tags": [ + "Annotations" + ], + "parameters": [ + { + "name": "paramOne", + "in": "query", + "description": "Description", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "paramTwo", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "paramThree", + "in": "query", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "paramFour", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "paramFive", + "in": "query", + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "paramSix", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "date" + } + }, + { + "name": "paramSeven", + "in": "query", + "schema": { + "type": "string", + "format": "time" + } + }, + { + "name": "paramEight", + "in": "query", + "required": true, + "schema": { + "type": "string", + "format": "time" + } + }, + { + "name": "paramNine", + "in": "query", + "schema": { + "$ref": "#/components/schemas/DateTimeKind" + } + }, + { + "name": "paramTen", + "in": "query", + "required": true, + "schema": { + "$ref": "#/components/schemas/DateTimeKind" + } + }, + { + "name": "paramEleven", + "in": "query", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "paramTwelve", + "in": "query", + "required": true, + "schema": { + "type": "number", + "format": "double" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AsParametersRecord" + } + } + } + } + } + } + }, "/WithOpenApi/weatherforecast": { "get": { "tags": [ @@ -266,6 +388,72 @@ }, "additionalProperties": false }, + "AsParametersRecord": { + "type": "object", + "properties": { + "paramOne": { + "type": "string", + "format": "uuid", + "nullable": true + }, + "paramTwo": { + "type": "string", + "format": "uuid" + }, + "paramThree": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "paramFour": { + "type": "string", + "format": "date-time" + }, + "paramFive": { + "type": "string", + "format": "date", + "nullable": true + }, + "paramSix": { + "type": "string", + "format": "date" + }, + "paramSeven": { + "type": "string", + "format": "time", + "nullable": true + }, + "paramEight": { + "type": "string", + "format": "time" + }, + "paramNine": { + "$ref": "#/components/schemas/DateTimeKind" + }, + "paramTen": { + "$ref": "#/components/schemas/DateTimeKind" + }, + "paramEleven": { + "type": "number", + "format": "double", + "nullable": true + }, + "paramTwelve": { + "type": "number", + "format": "double" + } + }, + "additionalProperties": false + }, + "DateTimeKind": { + "enum": [ + 0, + 1, + 2 + ], + "type": "integer", + "format": "int32" + }, "Fruit": { "type": "object", "properties": { diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs index 491237929d..34b3c63acd 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs @@ -339,7 +339,8 @@ public void GetSwagger_GenerateParametersSchemas_ForProvidedOpenApiOperation() new ApiParameterDescription { Name = "ParameterInMetadata", - ModelMetadata = ModelMetadataFactory.CreateForType(typeof(string)) + ModelMetadata = ModelMetadataFactory.CreateForType(typeof(string)), + Type = typeof(string) } }), } @@ -604,7 +605,8 @@ public void GetSwagger_GenerateParametersSchemas_IfActionParameterIsIllegalHeade { Name = "param", Source = BindingSource.Header, - ModelMetadata = ModelMetadataFactory.CreateForType(typeof(string)) + ModelMetadata = ModelMetadataFactory.CreateForType(typeof(string)), + Type = typeof(string) } }), } diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGeneratorVerifyTests/SwaggerGeneratorVerifyTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGeneratorVerifyTests/SwaggerGeneratorVerifyTests.cs index 1155cb3f51..f3728adcee 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGeneratorVerifyTests/SwaggerGeneratorVerifyTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGeneratorVerifyTests/SwaggerGeneratorVerifyTests.cs @@ -272,7 +272,8 @@ public Task ActionWithParameterAndProvidedOpenApiOperation() new ApiParameterDescription { Name = "ParameterInMetadata", - ModelMetadata = ModelMetadataFactory.CreateForType(typeof(string)) + ModelMetadata = ModelMetadataFactory.CreateForType(typeof(string)), + Type = typeof(string) } }), } @@ -580,7 +581,8 @@ public Task ActionParameterIsIllegalHeaderParameterWithProvidedOpenApiOperation( { Name = "param", Source = BindingSource.Header, - ModelMetadata = ModelMetadataFactory.CreateForType(typeof(string)) + ModelMetadata = ModelMetadataFactory.CreateForType(typeof(string)), + Type = typeof(string) } }), } diff --git a/test/Swashbuckle.AspNetCore.TestSupport/ApiExplorer/ApiDescriptionFactory.cs b/test/Swashbuckle.AspNetCore.TestSupport/ApiExplorer/ApiDescriptionFactory.cs index 0f7a0b782b..67f4e869ee 100644 --- a/test/Swashbuckle.AspNetCore.TestSupport/ApiExplorer/ApiDescriptionFactory.cs +++ b/test/Swashbuckle.AspNetCore.TestSupport/ApiExplorer/ApiDescriptionFactory.cs @@ -48,6 +48,7 @@ public static ApiDescription Create( if (parameterDescriptorWithParameterInfo != null && parameter.ModelMetadata == null) { parameter.ModelMetadata = ModelMetadataFactory.CreateForParameter(parameterDescriptorWithParameterInfo.ParameterInfo); + parameter.Type = parameter.ModelMetadata.ModelType; } apiDescription.ParameterDescriptions.Add(parameter); @@ -67,7 +68,7 @@ public static ApiDescription Create( foreach (var responseType in supportedResponseTypes) { // If the provided action has a return value AND the response status is 2XX - use it to assign ModelMetadata - if (methodInfo.ReturnType != null && responseType.StatusCode/100 == 2) + if (methodInfo.ReturnType != null && responseType.StatusCode / 100 == 2) { responseType.ModelMetadata = ModelMetadataFactory.CreateForType(methodInfo.ReturnType); } diff --git a/test/WebSites/WebApi/EndPoints/SwaggerAnnotationsEndpoints.cs b/test/WebSites/WebApi/EndPoints/SwaggerAnnotationsEndpoints.cs index 85508afce1..fe210869a1 100644 --- a/test/WebSites/WebApi/EndPoints/SwaggerAnnotationsEndpoints.cs +++ b/test/WebSites/WebApi/EndPoints/SwaggerAnnotationsEndpoints.cs @@ -16,6 +16,11 @@ public static IEndpointRouteBuilder MapAnnotationsEndpoints(this IEndpointRouteB group.MapPost("/fruit/{id}", CreateFruit); + group.MapGet("/AsParameters", ([AsParameters] AsParametersRecord record) => + { + return record; + }); + return app; } @@ -29,4 +34,18 @@ record struct CreateFruitModel [SwaggerSchema("Description for Schema")] record Fruit(string Name); + + record class AsParametersRecord( + [SwaggerParameter(Description = "Description")] Guid? paramOne, + Guid paramTwo, + DateTime? paramThree, + DateTime paramFour, + DateOnly? paramFive, + DateOnly paramSix, + TimeOnly? paramSeven, + TimeOnly paramEight, + DateTimeKind? paramNine, + DateTimeKind paramTen, + decimal? paramEleven, + decimal paramTwelve); }