open-api
open-api copied to clipboard
[openapi-request-validator] Validating multipart/form-data requests
This is related, but different from #710. I'm not sure that there is a bug, maybe I just need use these packages differently as I'm using them at quite low level (with manual parsing of request body and etc).
The problem
In OpenAPI 3 multipart/form-data
requests should be declared as objects in requestBody
according to Describing Request Body / File Uploads docs:
openapi: 3.0.3
info:
title: Sample Application API
version: v1
paths:
/fileAttachments:
post:
requestBody:
content:
multipart/form-data:
schema:
type: object
properties:
someId:
type: string
nullable: false
status:
type: string
nullable: true
file:
type: string
format: binary
nullable: false
responses:
204:
description: Success
I'm trying to validate such a request on AWS Lambda. Due to platform validations, I'm getting raw body, so I parse it with lambda-multipart-parser and validate with openapi-request-validator with code like this:
import OpenAPIRequestValidator } from "openapi-request-validator"
import { parse as parseMultipart } from "lambda-multipart-parser"
const { files, ...plainFields } = await parseMultipart(event)
const fileFields = Object.fromEntries(files.map(file => [file.fieldname, file.content.toString()]))
data = { ...plainFields, ...fileFields }
(new OpenAPIRequestValidator({…})).validateRequest({
headers: headers,
body: data,
params: event.pathParameters,
query: event.queryStringParameters,
})
Which gives me following object as a body
:
{
someId: "deadbeef",
status: "whatever",
file: '�PNG\r\n…',
}
Expected result
openapi-request-validator
validates request and doesn't pass requests with file
or someId
missing
Actual result
openapi-request-validator
passes requests with missing non-nullable fields
Workaround
Then I went to openapi-request-validator source and found that it expects for multipart requests that all input fields should be declared as input formdata
parameters instead of request body (see this test case)
So I tried to change schema in a way that openapi-request-validator should understand: replaced request body with parameters
apiSchema.yml with formData parameters
openapi: 3.0.3
info:
title: Sample Application API
version: v1
paths:
/fileAttachments:
post:
parameters:
- name: someId
in: formData
required: true
schema:
type: string
- name: status
in: formData
required: false
schema:
type: string
- name: file
in: formData
required: true
schema:
type: string
format: binary
responses:
204:
description: Success
And success! Now it is being validated as expected.
But Swagger Editor tells me that this schema is now invalid:
Structural error at paths./fileAttachments.post.parameters.0.in
should be equal to one of the allowed values
allowedValues: path, query, header, cookie
(and 2 more errors)
To complete this workaround I had to preprocess schema, converting multipart bodies into formData
parameters on schema load with code like this.
Some hacky TypeScript code
import yaml from "js-yaml"
import { readFileSync } from "fs"
import $RefParser from "@apidevtools/json-schema-ref-parser"
import { OpenAPIV3 } from "openapi-types"
type OpenApiHttpMethods = "get" | "post" | "put" | "delete" | "options" | "head" | "patch" | "trace"
const apiSpec = <OpenAPIV3.Document>yaml.safeLoad(readFileSync("./apiSchema.yaml").toString())
async function preprocessOpenapiSpec(apiSpec: OpenAPIV3.Document): Promise<OpenAPIV3.Document> {
const schema = <OpenAPIV3.Document>await $RefParser.dereference(apiSpec)
// Replace multipart request body with formData parameters for validation
for (const methods of Object.values(schema.paths)) {
for (const method of ["get", "post", "put", "delete", "options", "head", "patch", "trace"]) {
const methodOptions = methods[<OpenApiHttpMethods>method]
if (!methodOptions) continue
const reqBody = <OpenAPIV3.RequestBodyObject | undefined>methodOptions?.requestBody
if (reqBody?.content && "multipart/form-data" in reqBody.content) {
const props = (<OpenAPIV3.SchemaObject>reqBody.content["multipart/form-data"].schema)?.properties
if (!props) continue
methodOptions.parameters ||= []
for (const [name, subschema] of Object.entries(props)) {
const required = ("nullable" in subschema) ? !subschema.nullable : false
methodOptions.parameters.push({ name, in: "formData", required, schema: subschema })
}
delete methodOptions?.requestBody
}
}
}
return schema
}
Question
Is there a bug in openapi-request-validator? If so, can you point me where to start fixing it, so I can prepare a pull request (I'm not sure where to start, honestly)
Or should I somehow preprocess schema or body using some other package from this suite?
Originally posted by @Envek in https://github.com/kogosoftwarellc/open-api/issues/710#issuecomment-788128653
+1 I am having a problem with this as well. Any updates?