From e3b01f859ac4b972b52351923eb215ea3e6ee91d Mon Sep 17 00:00:00 2001 From: Carmine DiMascio Date: Sun, 16 Feb 2020 18:01:21 -0500 Subject: [PATCH 1/5] example with es6 modules export --- examples/3-eov-operations-babel/src/routes/ping.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/3-eov-operations-babel/src/routes/ping.js b/examples/3-eov-operations-babel/src/routes/ping.js index 4ed18858..36cf961d 100644 --- a/examples/3-eov-operations-babel/src/routes/ping.js +++ b/examples/3-eov-operations-babel/src/routes/ping.js @@ -1,3 +1 @@ -export default { - ping: (req, res) => res.status(200).send('pong'), -}; +export const ping = (req, res) => res.status(200).send('pong'); From 00273f78aaa12898ddb42df41071a7d9dab6fb44 Mon Sep 17 00:00:00 2001 From: Carmine DiMascio Date: Sun, 16 Feb 2020 18:01:41 -0500 Subject: [PATCH 2/5] fix oId check --- src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 1f500e96..fa2850b0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -240,7 +240,7 @@ export class OpenApiValidator { const modulePath = path.join(this.options.operationHandlers, baseName); if (!tmpModules[modulePath]) { tmpModules[modulePath] = require(modulePath); - if (!tmpModules[modulePath].oId) { + if (!tmpModules[modulePath][oId]) { // if oId is not found only module, try the module's default export tmpModules[modulePath] = tmpModules[modulePath].default; } From 2b0216912cdb32fe39e96ca01685929616d52ba3 Mon Sep 17 00:00:00 2001 From: Carmine DiMascio Date: Sun, 16 Feb 2020 18:01:55 -0500 Subject: [PATCH 3/5] increment patch version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4c075c33..d7c14711 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "express-openapi-validator", - "version": "3.7.1", + "version": "3.7.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 046a5ce3..12e9a504 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "express-openapi-validator", - "version": "3.7.1", + "version": "3.7.2", "description": "Automatically validate API requests and responses with OpenAPI 3 and Express.", "main": "dist/index.js", "scripts": { From e6f1926e5b0fa187ef8bf4ab3d8f6d96f7c09af8 Mon Sep 17 00:00:00 2001 From: Carmine DiMascio Date: Fri, 21 Feb 2020 21:41:27 -0500 Subject: [PATCH 4/5] #237 improve binary support --- src/middlewares/openapi.multipart.ts | 38 ++++++++++++++++++++++----- test/assets/image.png | Bin 0 -> 3850 bytes test/multipart.spec.ts | 15 ++++++++++- test/resources/multipart.yaml | 13 +++++++++ 4 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 test/assets/image.png diff --git a/src/middlewares/openapi.multipart.ts b/src/middlewares/openapi.multipart.ts index fc0ff6ac..ada81fae 100644 --- a/src/middlewares/openapi.multipart.ts +++ b/src/middlewares/openapi.multipart.ts @@ -1,24 +1,27 @@ import { OpenApiContext } from '../framework/openapi.context'; +import { createRequestAjv } from '../framework/ajv'; import { validationError } from './util'; -import { Request } from 'express'; import { + OpenAPIV3, OpenApiRequest, OpenApiRequestHandler, ValidationError, } from '../framework/types'; import { MulterError } from 'multer'; +import ajv = require('ajv'); const multer = require('multer'); export function multipart( - OpenApiContext: OpenApiContext, + context: OpenApiContext, multerOpts: {}, ): OpenApiRequestHandler { const mult = multer(multerOpts); + const Ajv = createRequestAjv(context.apiDoc, {}); return (req, res, next) => { // TODO check that format: binary (for upload) else do not use multer.any() // use multer.none() if no binary parameters exist - if (isMultipart(req) && isValidContentType(req)) { + if (shouldHandle(Ajv, req)) { mult.any()(req, res, err => { if (err) { next(error(req, err)); @@ -66,9 +69,32 @@ export function multipart( }; } -function isValidContentType(req: Request): boolean { - const contentType = req.headers['content-type']; - return !contentType || contentType.includes('multipart/form-data'); +function shouldHandle(Ajv, req: OpenApiRequest): boolean { + const reqContentType = req.headers['content-type']; + if (isMultipart(req) && reqContentType?.includes('multipart/form-data')) { + return true; + } + + const bodyRef = (req?.openapi)?.schema?.$ref; + const requestBody = bodyRef + ? Ajv.getSchema(bodyRef) + : (req?.openapi)?.schema?.requestBody; + const tmp = requestBody?.content; + if (!tmp) return false; + + const content = <{ [media: string]: OpenAPIV3.MediaTypeObject }>tmp; + const contentTypes = Object.entries(content); + for (const [contentType, mediaType] of contentTypes) { + if (!contentType.includes(reqContentType)) continue; + const mediaTypeSchema = mediaType?.schema; + const schema = mediaTypeSchema?.$ref + ? Ajv.getSchema(mediaTypeSchema.$ref) + : mediaTypeSchema; + const format = schema?.format; + if (format === 'binary') { + return true; + } + } } function isMultipart(req: OpenApiRequest): boolean { diff --git a/test/assets/image.png b/test/assets/image.png new file mode 100644 index 0000000000000000000000000000000000000000..450b600e627bd7315527168afbaf4c7fdc3f67ed GIT binary patch literal 3850 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 Inz~2)7Ya0r?EnA( literal 0 HcmV?d00001 diff --git a/test/multipart.spec.ts b/test/multipart.spec.ts index ec0b424e..99b937ef 100644 --- a/test/multipart.spec.ts +++ b/test/multipart.spec.ts @@ -1,5 +1,6 @@ import * as express from 'express'; import * as path from 'path'; +import * as fs from 'fs'; import { expect } from 'chai'; import * as request from 'supertest'; import { createApp } from './common/app'; @@ -33,7 +34,8 @@ describe(packageJson.name, () => { metadata: req.body.metadata, }); }) - .post(`/sample_1`, (req, res) => res.json(req.body)), + .post(`/sample_1`, (req, res) => res.json(req.body)) + .post(`/sample_3`, (req, res) => res.json(req.body)), ), ); }); @@ -67,6 +69,17 @@ describe(packageJson.name, () => { .attach('file', 'package.json') .expect(400)); + it('should validate application/octet-stream file and metadata', done => { + const testImage = `${__dirname}/assets/image.png`; + const req = request(app) + .post(`${app.basePath}/sample_3`) + .set('content-type', 'application/octet-stream'); + + const imgStream = fs.createReadStream(testImage); + imgStream.on('end', () => req.end(done)); + imgStream.pipe(req, { end: false }); + }); + it('should validate multipart file and metadata', async () => { await request(app) .post(`${app.basePath}/sample_2`) diff --git a/test/resources/multipart.yaml b/test/resources/multipart.yaml index c1a9770e..1bf7a0c0 100644 --- a/test/resources/multipart.yaml +++ b/test/resources/multipart.yaml @@ -74,6 +74,19 @@ paths: responses: "200": description: form data + /sample_3: + post: + description: upload a photo of the pet + operationId: octetStream + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: binary /range: get: parameters: From a2025a9e9217fce6677f31b049b4d8d399b1531f Mon Sep 17 00:00:00 2001 From: Carmine DiMascio Date: Fri, 21 Feb 2020 21:44:06 -0500 Subject: [PATCH 5/5] fix var name --- src/middlewares/openapi.multipart.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/middlewares/openapi.multipart.ts b/src/middlewares/openapi.multipart.ts index ada81fae..d9a050ec 100644 --- a/src/middlewares/openapi.multipart.ts +++ b/src/middlewares/openapi.multipart.ts @@ -79,10 +79,10 @@ function shouldHandle(Ajv, req: OpenApiRequest): boolean { const requestBody = bodyRef ? Ajv.getSchema(bodyRef) : (req?.openapi)?.schema?.requestBody; - const tmp = requestBody?.content; - if (!tmp) return false; + const bodyContent = requestBody?.content; + if (!bodyContent) return false; - const content = <{ [media: string]: OpenAPIV3.MediaTypeObject }>tmp; + const content = <{ [media: string]: OpenAPIV3.MediaTypeObject }>bodyContent; const contentTypes = Object.entries(content); for (const [contentType, mediaType] of contentTypes) { if (!contentType.includes(reqContentType)) continue;