From b74ce2aa330b6a14a1297e90ba2fde63b9db8047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Garc=C3=ADa=20de=20la=20Noceda=20Arg=C3=BCelles?= Date: Wed, 2 Oct 2024 16:51:57 +0200 Subject: [PATCH] Generate Properties whenever there are objects without schema(Not only IFormFile or IFormFileCollection) --- .../SwaggerGenerator/SwaggerGenerator.cs | 18 +++--- ...r_WebApi_swaggerRequestUri=v1.verified.txt | 61 +++++++++++++++++++ ...est.TypesAreRenderedCorrectly.verified.txt | 61 +++++++++++++++++++ .../SwaggerGenerator/SwaggerGeneratorTests.cs | 61 +++++++++++++++++++ ...piOperationWithStringFromForm.verified.txt | 52 ++++++++++++++++ .../SwaggerGeneratorVerifyTests.cs | 51 ++++++++++++++++ .../WebApi/EndPoints/OpenApiEndpoints.cs | 5 ++ 7 files changed, 298 insertions(+), 11 deletions(-) create mode 100644 test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGeneratorVerifyTests/SwaggerGeneratorVerifyTests.GetSwagger_GenerateConsumesSchemas_ForProvidedOpenApiOperationWithStringFromForm.verified.txt diff --git a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs index f5207082eb..fa6f178cc5 100644 --- a/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs +++ b/src/Swashbuckle.AspNetCore.SwaggerGen/SwaggerGenerator/SwaggerGenerator.cs @@ -480,19 +480,15 @@ static OpenApiSchema GenerateSchemaIncludingFormFile(ApiParameterDescription api if (generatedSchema.Reference is null && apiParameterDescription.IsFromForm()) { mediaType.Encoding.Add(apiParameterDescription.Name, new OpenApiEncoding { Style = ParameterStyle.Form }); - if ((generatedSchema.Type == "string" && generatedSchema.Format == "binary") - || (generatedSchema.Type == "array" && generatedSchema.Items.Type == "string" && generatedSchema.Items.Format == "binary")) + return new OpenApiSchema() { - return new OpenApiSchema() + Type = "object", + Properties = new Dictionary() { - Type = "object", - Properties = new Dictionary() - { - [apiParameterDescription.Name] = generatedSchema - }, - Required = apiParameterDescription.IsRequired ? new SortedSet() { apiParameterDescription.Name } : null - }; - } + [apiParameterDescription.Name] = generatedSchema + }, + Required = apiParameterDescription.IsRequired ? new SortedSet() { apiParameterDescription.Name } : null + }; } return generatedSchema; } 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 84cdebb45c..0201aeb342 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 @@ -370,6 +370,67 @@ } } }, + "/WithOpenApi/IFromFileAndString": { + "post": { + "tags": [ + "WithOpenApi" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "allOf": [ + { + "required": [ + "file" + ], + "type": "object", + "properties": { + "file": { + "type": "string", + "format": "binary" + } + } + }, + { + "required": [ + "tags" + ], + "type": "object", + "properties": { + "tags": { + "type": "string" + } + } + } + ] + }, + "encoding": { + "file": { + "style": "form" + }, + "tags": { + "style": "form" + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/XmlComments/Car/{id}": { "get": { "tags": [ diff --git a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.TypesAreRenderedCorrectly.verified.txt b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.TypesAreRenderedCorrectly.verified.txt index 84cdebb45c..0201aeb342 100644 --- a/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.TypesAreRenderedCorrectly.verified.txt +++ b/test/Swashbuckle.AspNetCore.IntegrationTests/SwaggerVerifyIntegrationTest.TypesAreRenderedCorrectly.verified.txt @@ -370,6 +370,67 @@ } } }, + "/WithOpenApi/IFromFileAndString": { + "post": { + "tags": [ + "WithOpenApi" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "allOf": [ + { + "required": [ + "file" + ], + "type": "object", + "properties": { + "file": { + "type": "string", + "format": "binary" + } + } + }, + { + "required": [ + "tags" + ], + "type": "object", + "properties": { + "tags": { + "type": "string" + } + } + } + ] + }, + "encoding": { + "file": { + "style": "form" + }, + "tags": { + "style": "form" + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, "/XmlComments/Car/{id}": { "get": { "tags": [ diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs index 0f76051b19..f4a4cb397d 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGenerator/SwaggerGeneratorTests.cs @@ -2260,6 +2260,67 @@ public void GetSwagger_GenerateConsumesSchemas_ForProvidedOpenApiOperationWithIF Assert.Equal(ParameterStyle.Form, content.Value.Encoding["param"].Style); } + [Fact] + public void GetSwagger_GenerateConsumesSchemas_ForProvidedOpenApiOperationWithStringFromForm() + { + var methodInfo = typeof(FakeController).GetMethod(nameof(FakeController.ActionWithConsumesAttribute)); + var actionDescriptor = new ActionDescriptor + { + EndpointMetadata = + [ + new OpenApiOperation + { + OperationId = "OperationIdSetInMetadata", + RequestBody = new() + { + Content = new Dictionary() + { + ["application/someMediaType"] = new() + } + } + } + ], + RouteValues = new Dictionary + { + ["controller"] = methodInfo.DeclaringType.Name.Replace("Controller", string.Empty) + } + }; + var subject = Subject( + apiDescriptions: + [ + ApiDescriptionFactory.Create( + actionDescriptor, + methodInfo, + groupName: "v1", + httpMethod: "POST", + relativePath: "resource", + parameterDescriptions: + [ + new ApiParameterDescription() + { + Name = "param", + Source = BindingSource.Form, + ModelMetadata = ModelMetadataFactory.CreateForType(typeof(string)) + } + ]), + ] + ); + + var document = subject.GetSwagger("v1"); + + Assert.Equal("OperationIdSetInMetadata", document.Paths["/resource"].Operations[OperationType.Post].OperationId); + var content = Assert.Single(document.Paths["/resource"].Operations[OperationType.Post].RequestBody.Content); + Assert.Equal("application/someMediaType", content.Key); + Assert.NotNull(content.Value.Schema); + Assert.Equal("object", content.Value.Schema.Type); + Assert.NotEmpty(content.Value.Schema.Properties); + Assert.NotNull(content.Value.Schema.Properties["param"]); + Assert.Equal("string", content.Value.Schema.Properties["param"].Type); + Assert.NotNull(content.Value.Encoding); + Assert.NotNull(content.Value.Encoding["param"]); + Assert.Equal(ParameterStyle.Form, content.Value.Encoding["param"].Style); + } + private static SwaggerGenerator Subject( IEnumerable apiDescriptions, SwaggerGeneratorOptions options = null, diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGeneratorVerifyTests/SwaggerGeneratorVerifyTests.GetSwagger_GenerateConsumesSchemas_ForProvidedOpenApiOperationWithStringFromForm.verified.txt b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGeneratorVerifyTests/SwaggerGeneratorVerifyTests.GetSwagger_GenerateConsumesSchemas_ForProvidedOpenApiOperationWithStringFromForm.verified.txt new file mode 100644 index 0000000000..e7c7a6ab9f --- /dev/null +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGeneratorVerifyTests/SwaggerGeneratorVerifyTests.GetSwagger_GenerateConsumesSchemas_ForProvidedOpenApiOperationWithStringFromForm.verified.txt @@ -0,0 +1,52 @@ +{ + Info: { + Title: Test API, + Version: V1 + }, + Paths: { + /resource: { + Operations: { + Post: { + OperationId: OperationIdSetInMetadata, + RequestBody: { + UnresolvedReference: false, + Required: false, + Content: { + application/someMediaType: { + Schema: { + Type: object, + ReadOnly: false, + WriteOnly: false, + Properties: { + param: { + Type: string, + ReadOnly: false, + WriteOnly: false, + AdditionalPropertiesAllowed: true, + Nullable: false, + Deprecated: false, + UnresolvedReference: false + } + }, + AdditionalPropertiesAllowed: true, + Nullable: false, + Deprecated: false, + UnresolvedReference: false + }, + Encoding: { + param: { + Style: Form + } + } + } + } + }, + Deprecated: false + } + }, + UnresolvedReference: false + } + }, + Components: {}, + HashCode: 7639B8A665AFC72F5C8D9ED02AA2E6416B9F82FDCC86D490FD248D3B657355F3993BD00384468E8D23DC0AC9FACECD425824F9596F6183EBDF974B9343CEDCF7 +} \ No newline at end of file diff --git a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGeneratorVerifyTests/SwaggerGeneratorVerifyTests.cs b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGeneratorVerifyTests/SwaggerGeneratorVerifyTests.cs index f3728adcee..09f9e44b58 100644 --- a/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGeneratorVerifyTests/SwaggerGeneratorVerifyTests.cs +++ b/test/Swashbuckle.AspNetCore.SwaggerGen.Test/SwaggerGeneratorVerifyTests/SwaggerGeneratorVerifyTests.cs @@ -1310,6 +1310,57 @@ public Task GetSwagger_GenerateConsumesSchemas_ForProvidedOpenApiOperationWithIF return Verifier.Verify(document); } + [Fact] + public Task GetSwagger_GenerateConsumesSchemas_ForProvidedOpenApiOperationWithStringFromForm() + { + var methodInfo = typeof(FakeController).GetMethod(nameof(FakeController.ActionWithConsumesAttribute)); + var actionDescriptor = new ActionDescriptor + { + EndpointMetadata = + [ + new OpenApiOperation + { + OperationId = "OperationIdSetInMetadata", + RequestBody = new() + { + Content = new Dictionary() + { + ["application/someMediaType"] = new() + } + } + } + ], + RouteValues = new Dictionary + { + ["controller"] = methodInfo.DeclaringType.Name.Replace("Controller", string.Empty) + } + }; + var subject = Subject( + apiDescriptions: + [ + ApiDescriptionFactory.Create( + actionDescriptor, + methodInfo, + groupName: "v1", + httpMethod: "POST", + relativePath: "resource", + parameterDescriptions: + [ + new ApiParameterDescription() + { + Name = "param", + Source = BindingSource.Form, + ModelMetadata = ModelMetadataFactory.CreateForType(typeof(string)) + } + ]), + ] + ); + + var document = subject.GetSwagger("v1"); + + return Verifier.Verify(document); + } + private static SwaggerGenerator Subject( IEnumerable apiDescriptions, SwaggerGeneratorOptions options = null, diff --git a/test/WebSites/WebApi/EndPoints/OpenApiEndpoints.cs b/test/WebSites/WebApi/EndPoints/OpenApiEndpoints.cs index f59eb08e1c..9f5f7e44b9 100644 --- a/test/WebSites/WebApi/EndPoints/OpenApiEndpoints.cs +++ b/test/WebSites/WebApi/EndPoints/OpenApiEndpoints.cs @@ -65,6 +65,11 @@ public static IEndpointRouteBuilder MapWithOpenApiEndpoints(this IEndpointRouteB return $"{dto}"; }).WithOpenApi(); + group.MapPost("/IFromFileAndString", (IFormFile file, [FromForm] string tags) => + { + return $"{file.FileName}{tags}"; + }).WithOpenApi(); + return app; } }