kogosoftwarellc / open-api

A Monorepo of various packages to power OpenAPI in node
MIT License
892 stars 235 forks source link

openapi v3 -style form data not being coerced #710

Closed chadxz closed 3 years ago

chadxz commented 3 years ago

In openapi v3, form data is now expressed as a part of the requestBody and not as parameters. As a result, any form data that is declared as a non-string type is not being coerced by openapi-framework.

I've traced the issue to this: https://github.com/kogosoftwarellc/open-api/blob/master/packages/openapi-framework/index.ts#L507-L512

namely, the coercer for my form data route is being constructed with only the parameters and not the requestBody.

Am I missing something or is this functionality currently missing and needs to be added?

chadxz commented 3 years ago

Pushed a PR #711 for this. Will probably need some mentorship on it though.

Envek commented 3 years ago

+1 on this issue! I've faced it too. I will describe it in more details (just in case it is actually different one maybe)


So, given valid Open API 3 spec file with multipart/form-data request, declared according to Describing Request Body / File Uploads docs:

apiSchema.yml with multipart/form-data request body ```yaml 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 ```

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


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 ```yaml 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)

Workaround: preprocess schema, converting multipart bodies into formData parameters on schema load with code like this.

Some hacky TypeScript code ```typescript 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 = yaml.safeLoad(readFileSync("./apiSchema.yaml").toString()) async function preprocessOpenapiSpec(apiSpec: OpenAPIV3.Document): Promise { const schema = 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[method] if (!methodOptions) continue const reqBody = methodOptions?.requestBody if (reqBody?.content && "multipart/form-data" in reqBody.content) { const props = (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 } ```
chadxz commented 3 years ago

Andrey, the pr I submitted would fix it for the url-encoded data, but not the multipart. I haven’t looked at all to see what would be required to fix that, but it’s likely not much more work.

On Mon, Mar 1, 2021 at 11:28 AM Andrey Novikov notifications@github.com wrote:

+1 on this issue! I've faced it too. I will describe it in more details (just in case it is actually different one maybe)

So, given valid Open API 3 spec file with multipart/form-data request, declared according to Describing Request Body / File Uploads https://swagger.io/docs/specification/describing-request-body/file-upload/ docs: apiSchema.yml with multipart/form-data request body

openapi: 3.0.3info: title: Sample Application API version: v1paths: /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

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

Then I went to openapi-request-validator https://github.com/kogosoftwarellc/open-api/tree/master/packages/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 https://github.com/kogosoftwarellc/open-api/blob/5f1275d1595f1c75ec331422a9eb3ff2e91a3549/packages/openapi-request-validator/test/data-driven/fail-a-missing-required-formData-property.js )

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.3info: title: Sample Application API version: v1paths: /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 https://editor.swagger.io/ 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)


Workaround: 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 = yaml.safeLoad(readFileSync("./apiSchema.yaml").toString()) async function preprocessOpenapiSpec(apiSpec: OpenAPIV3.Document): Promise { const schema = 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[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}

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/kogosoftwarellc/open-api/issues/710#issuecomment-788128653, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACLPYYGVCHXPM3NYGNT3G3TBPFFDANCNFSM4YG2LWLA .

chadxz commented 3 years ago

Closing this, merged PR fixed the issue I was experiencing. @Envek feel free to open a new issue for your specific problem