Description
A little backstory - operations which contain a multipart/form-data
request_body , which within themselves contain an array field don't get sent as arrays via SwaggerUI. curl
is being generated without appending to the array field:
operation(
.....
request_body:
{"Multipart form", "multipart/form-data",
MyApp.Multipart, required: true},
....
def Multipart do
...
OpenApiSpex.schema(%{
type: :object,
properties: %{
array_items: %Schema{
description: """
array of files
""",
type: :array,
minItems: 1,
items: %Schema{
type: :string,
format: :binary
}
}
}
}
This generates the following Swagger UI:
Note the curl
here. array_items
needs to be array_items[]
in order for Plug to create an array of Plug.Upload
constructs. Without this, the conn object contains:
"array_items" => %Plug.Upload{
content_type: "application/pdf",
filename: "empty_pdf_2.pdf",
path: "/var/folders/lx/8gvgf9jd24x6mcc11g747j500000gn/T//plug-1648/multipart-1648671114-838101121406784-2"
}
This is a known issue with swagger. The solution has been to add brackets to the name of the fields so that curl interpolates currently. See https://stackoverflow.com/questions/53213067/swagger-ui-open-api-3-multipart-form-data-array-problem
Taking that curl from the example above and modifying to array_items[]
creates the array as expected:
"array_items" => [
%Plug.Upload{
content_type: "application/pdf",
filename: "empty_pdf_1.pdf",
path: "/var/folders/lx/8gvgf9jd24x6mcc11g747j500000gn/T//plug-1648/multipart-1648671649-87151574074633-4"
},
%Plug.Upload{
content_type: "application/pdf",
filename: "empty_pdf_2.pdf",
path: "/var/folders/lx/8gvgf9jd24x6mcc11g747j500000gn/T//plug-1648/multipart-1648671649-724027952677069-4"
}
]
To resolve this, I've got a hacky solution that involves changing the field name to include brackets:
OpenApiSpex.schema(%{
type: :object,
properties: %{
:"array_items[]": %Schema{
description: """
array of files
""",
type: :array,
minItems: 1,
items: %Schema{
type: :string,
format: :binary
}
}
}
}
But downstream, validations will fail with required
fields for :"array_items[]"
due to the way this ends up getting interpolated. Plug.Conn.Query.decode
cuts out brackets. If I end up just putting :array_items
in the required field, I won't be able to submit my request in Swagger due to missing a required field (Swagger sees array_items[] not array_items)
OpenApiSpex.schema(%{
type: :object,
properties: %{
:"array_items[]": %Schema{
description: """
array of files
""",
type: :array,
minItems: 1,
items: %Schema{
type: :string,
format: :binary
}
}
},
required: [:"array_items[]"]
}
..... open api request sent, request passes through MULTIPART parser, which runs through Plug.Conn.Query for decoding array_items[] to array_items list
params: %{
"array_items" => [
%Plug.Upload{
content_type: "application/pdf",
filename: "empty_pdf_1.pdf",
path: "/var/folders/lx/8gvgf9jd24x6mcc11g747j500000gn/T//plug-1648/multipart-1648673751-336866617448758-3"
},
%Plug.Upload{
content_type: "application/pdf",
filename: "empty_pdf_2.pdf",
path: "/var/folders/lx/8gvgf9jd24x6mcc11g747j500000gn/T//plug-1648/multipart-1648673751-11087878838975-3"
}
]
}
....
.... validation error
{
"errors": [
{
"detail": "Missing field: array_items[]",
"source": {
"pointer": "/array_items[]"
},
"title": "Invalid value"
}
]
}
Ideally validations follow the conventions for removing brackets on field names for multipart
data. Ergo, if required: [:"array_items[]"]
is set, on validation it will be checked against array_items
EDIT: It's Plug.Conn.Query.decode
that cuts out brackets from field names.
iex(1)> Plug.Conn.Query.decode "a[]=1&a[]=2"
%{"a" => ["1", "2"]}
iex(2)> Plug.Conn.Query.encode %{"a" => ["1", "2"]}
"a[]=1&a[]=2"
iex(3)> Plug.Conn.Query.decode "a[1]=1&a[2]=2"
%{"a" => %{"1" => "1", "2" => "2"}}
iex(4)> Plug.Conn.Query.encode %{"a" => %{"1" => "1", "2" => "2"}}
"a[1]=1&a[2]=2"