domaindrivendev / Swashbuckle.AspNetCore

Swagger tools for documenting API's built on ASP.NET Core
MIT License
5.26k stars 1.32k forks source link

[Bug]: [FromForm] does not generate a property in schema but makes itself the schema #3094

Closed warappa closed 1 month ago

warappa commented 1 month ago

Describe the bug

I have a Minimal API. In previous versions of this library I had to patch the generated schema to have tags in the schema next to file (-> properties of schema object).

I heard, in the newer versions of this library [FromForm] is better supported, so I tried that. Unfortunately, one of my endpoints relying on [FromForm] yields a schema that is incorrect.

I have a Minimal API with the following action:

app.MapPost(
    "{tool}/{scenario}/{fileName}",
    async (
        string tool,
        string scenario,
        string fileName,
        [FromForm(Name = "tags")] string tags,
        IFormFile file,
        HttpRequest request) =>
    {
        return Results.Ok();
    })
.WithName("Upload")
.DisableAntiforgery()
.WithOpenApi();

[!NOTE] A simpler version without file also shows this behavior.

Expected behavior

It should generate:

{
    "operationId": "Upload",
    "parameters": [
        {
            "name": "tool",
            "in": "path",
            "required": true,
            "schema": {
                "type": "string"
            }
        },
        {
            "name": "scenario",
            "in": "query",
            "required": true,
            "schema": {
                "type": "string"
            }
        },
        {
            "name": "fileName",
            "in": "path",
            "required": true,
            "schema": {
                "type": "string"
            }
        }
    ],
    "requestBody": {
        "content": {
            "multipart/form-data": {
                "schema": {
                    "required": [
                        "file"
                    ],
                    "type": "object",
                    "properties": {
                        "file": {
                            "type": "string",
                            "format": "binary"
                        },
                        "tags": {
                            "type": "string"
                        }
                    }
                },
                "encoding": {
                    "tags": {
                        "style": "form"
                    },
                    "file": {
                        "style": "form"
                    }
                }
            }
        },
        "required": true
    },
    "responses": {
        "200": {
            "description": "OK"
        }
    }
}

[!NOTE] Please note the file and tags properties in the schema. This way I can send a tags string alongside the binary file payload.

[!NOTE] tags in encoding is just a guess on my side.

Actual behavior

It generates:

"post": {
    "operationId": "Upload",
    "parameters": [
        {
            "name": "tool",
            "in": "path",
            "required": true,
            "schema": {
                "type": "string"
            }
        },
        {
            "name": "scenario",
            "in": "query",
            "required": true,
            "schema": {
                "type": "string"
            }
        },
        {
            "name": "fileName",
            "in": "path",
            "required": true,
            "schema": {
                "type": "string"
            }
        }
    ],
    "requestBody": {
        "content": {
            "multipart/form-data": {
                "schema": {
                    "allOf": [
                        {
                            "type": "string"
                        },
                        {
                            "required": [
                                "file"
                            ],
                            "type": "object",
                            "properties": {
                                "file": {
                                    "type": "string",
                                    "format": "binary"
                                }
                            }
                        }
                    ]
                },
                "encoding": {
                    "tags": {
                        "style": "form"
                    },
                    "file": {
                        "style": "form"
                    }
                }
            }
        },
        "required": true
    },
    "responses": {
        "200": {
            "description": "OK"
        }
    }
}

This schema would mean, that the body is a mix of a string (tags) and something with a property file that is a binary file (⚠ note the allOf). That is incorrect.

[!NOTE] A simpler version without file also shows this behavior (but with skipping allOf).

[!NOTE] As to be expected, Swagger UI is also not able to display a tags input anywhere

Steps to reproduce

  1. Create new minimal API project with .NET 8 from template
  2. Configure to use swagger gen
  3. Add endpoint like in example
  4. Update Swagger libraries
  5. Browse to the generated swagger.json and inspect generated OpenAPI schema

Exception(s) (if any)

No response

Swashbuckle.AspNetCore version

6.8.1

.NET Version

net8.0

Anything else?

No response

jgarciadelanoceda commented 1 month ago

I am working on it. It's a fix over the PRs: #2979, #2972 and #2963. The problem that the previous version had is that if there is no schema it has to create the properties and so on. The AllOf is OK (Because the implementation of Microsoft.AspNetCore.OpenApi does just the same (if there is more than one element then it creates an AllOf node.

This is how it's going to get rendered:

"post": {
    "operationId": "Upload",
    "parameters": [
        {
            "name": "tool",
            "in": "path",
            "required": true,
            "schema": {
                "type": "string"
            }
        },
        {
            "name": "scenario",
            "in": "query",
            "required": true,
            "schema": {
                "type": "string"
            }
        },
        {
            "name": "fileName",
            "in": "path",
            "required": true,
            "schema": {
                "type": "string"
            }
        }
    ],
    "requestBody": {
        "content": {
            "multipart/form-data": {
                "schema": {
                    "allOf": [
                        {
                            "type": "object",
                            "properties": {
                                "tags": {
                                    "type": "string"
                                }
                            }
                        },
                        {
                            "required": [
                                "file"
                            ],
                            "type": "object",
                            "properties": {
                                "file": {
                                    "type": "string",
                                    "format": "binary"
                                }
                            }
                        }
                    ]
                },
                "encoding": {
                    "tags": {
                        "style": "form"
                    },
                    "file": {
                        "style": "form"
                    }
                }
            }
        },
        "required": true
    },
    "responses": {
        "200": {
            "description": "OK"
        }
    }
}