From 46ed8fb1a517ac3207b646bc0dfa3f274346961c Mon Sep 17 00:00:00 2001 From: LEI Date: Sun, 8 Nov 2020 23:42:38 +0100 Subject: [PATCH 1/3] fix: request schema preprocessor unknown format error --- src/openapi.validator.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/openapi.validator.ts b/src/openapi.validator.ts index 34cf86ce..6382c629 100644 --- a/src/openapi.validator.ts +++ b/src/openapi.validator.ts @@ -85,6 +85,7 @@ export class OpenApiValidator { } installMiddleware(spec: Promise): OpenApiRequestHandler[] { + const { formats } = this.options; const middlewares: OpenApiRequestHandler[] = []; const pContext = spec.then((spec) => { const responseApiDoc = this.options.validateResponses @@ -97,6 +98,13 @@ export class OpenApiValidator { useDefaults: true, unknownFormats: this.options.unknownFormats, format: this.options.validateFormats, + formats: formats.reduce((acc, f) => { + acc[f.name] = { + type: f.type, + validate: f.validate, + }; + return acc; + }, {}), }).preProcess(); return { From e60315e96a8dc560d301d8f7d772b9b10d295246 Mon Sep 17 00:00:00 2001 From: LEI Date: Mon, 9 Nov 2020 00:13:32 +0100 Subject: [PATCH 2/3] fix: multipart middleware ajv options formats and types --- src/framework/types.ts | 5 +++-- src/middlewares/openapi.multipart.ts | 4 +--- src/openapi.validator.ts | 15 ++++++++++++--- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/framework/types.ts b/src/framework/types.ts index 90366c87..208bb6d1 100644 --- a/src/framework/types.ts +++ b/src/framework/types.ts @@ -31,8 +31,9 @@ export type SecurityHandlers = { ) => boolean | Promise; }; -export interface MultipartOpts extends ajv.Options { - multerOpts: any; +export interface MultipartOpts { + multerOpts: boolean | multer.Options; + ajvOpts: ajv.Options; } export interface RequestValidatorOptions diff --git a/src/middlewares/openapi.multipart.ts b/src/middlewares/openapi.multipart.ts index ffe4c1dc..52a071bf 100644 --- a/src/middlewares/openapi.multipart.ts +++ b/src/middlewares/openapi.multipart.ts @@ -19,9 +19,7 @@ export function multipart( options: MultipartOpts, ): OpenApiRequestHandler { const mult = multer(options.multerOpts); - const Ajv = createRequestAjv(apiDoc, { - unknownFormats: options.unknownFormats, - }); + const Ajv = createRequestAjv(apiDoc, { ...options.ajvOpts }); 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 diff --git a/src/openapi.validator.ts b/src/openapi.validator.ts index 6382c629..f6a89879 100644 --- a/src/openapi.validator.ts +++ b/src/openapi.validator.ts @@ -85,7 +85,6 @@ export class OpenApiValidator { } installMiddleware(spec: Promise): OpenApiRequestHandler[] { - const { formats } = this.options; const middlewares: OpenApiRequestHandler[] = []; const pContext = spec.then((spec) => { const responseApiDoc = this.options.validateResponses @@ -98,7 +97,7 @@ export class OpenApiValidator { useDefaults: true, unknownFormats: this.options.unknownFormats, format: this.options.validateFormats, - formats: formats.reduce((acc, f) => { + formats: this.options.formats.reduce((acc, f) => { acc[f.name] = { type: f.type, validate: f.validate, @@ -257,7 +256,17 @@ export class OpenApiValidator { private multipartMiddleware(apiDoc: OpenAPIV3.Document) { return middlewares.multipart(apiDoc, { multerOpts: this.options.fileUploader, - unknownFormats: this.options.unknownFormats, + ajvOpts: { + unknownFormats: this.options.unknownFormats, + format: this.options.validateFormats, + formats: this.options.formats.reduce((acc, f) => { + acc[f.name] = { + type: f.type, + validate: f.validate, + }; + return acc; + }, {}), + }, }); } From 3bf8a1a461a9fc0dd5682414b2b733b56159f761 Mon Sep 17 00:00:00 2001 From: Carmine DiMascio Date: Sun, 8 Nov 2020 20:38:24 -0500 Subject: [PATCH 3/3] common ajv options handler --- src/openapi.validator.ts | 144 +++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 74 deletions(-) diff --git a/src/openapi.validator.ts b/src/openapi.validator.ts index f6a89879..eb286c80 100644 --- a/src/openapi.validator.ts +++ b/src/openapi.validator.ts @@ -1,4 +1,5 @@ import ono from 'ono'; +import ajv = require('ajv'); import * as express from 'express'; import * as _uniq from 'lodash.uniq'; import * as cloneDeep from 'lodash.clonedeep'; @@ -15,6 +16,7 @@ import { OpenApiRequestMetadata, ValidateSecurityOpts, OpenAPIV3, + RequestValidatorOptions, } from './framework/types'; import { defaultResolver } from './resolvers'; import { OperationHandlerOptions } from './framework/types'; @@ -35,6 +37,7 @@ export { export class OpenApiValidator { readonly options: OpenApiValidatorOpts; + readonly ajvOpts: AjvOptions; constructor(options: OpenApiValidatorOpts) { this.validateOptions(options); @@ -82,6 +85,7 @@ export class OpenApiValidator { } this.options = options; + this.ajvOpts = new AjvOptions(options); } installMiddleware(spec: Promise): OpenApiRequestHandler[] { @@ -90,21 +94,10 @@ export class OpenApiValidator { const responseApiDoc = this.options.validateResponses ? cloneDeep(spec.apiDoc) : null; - new RequestSchemaPreprocessor(spec.apiDoc, { - nullable: true, - coerceTypes: this.options.coerceTypes, - removeAdditional: false, - useDefaults: true, - unknownFormats: this.options.unknownFormats, - format: this.options.validateFormats, - formats: this.options.formats.reduce((acc, f) => { - acc[f.name] = { - type: f.type, - validate: f.validate, - }; - return acc; - }, {}), - }).preProcess(); + new RequestSchemaPreprocessor( + spec.apiDoc, + this.ajvOpts.preprocessor, + ).preProcess(); return { context: new OpenApiContext(spec, this.options.ignorePaths), @@ -256,17 +249,7 @@ export class OpenApiValidator { private multipartMiddleware(apiDoc: OpenAPIV3.Document) { return middlewares.multipart(apiDoc, { multerOpts: this.options.fileUploader, - ajvOpts: { - unknownFormats: this.options.unknownFormats, - format: this.options.validateFormats, - formats: this.options.formats.reduce((acc, f) => { - acc[f.name] = { - type: f.type, - validate: f.validate, - }; - return acc; - }, {}), - }, + ajvOpts: this.ajvOpts.multipart, }); } @@ -278,59 +261,18 @@ export class OpenApiValidator { } private requestValidationMiddleware(apiDoc: OpenAPIV3.Document) { - const { - coerceTypes, - unknownFormats, - validateRequests, - validateFormats, - formats, - } = this.options; - const { allowUnknownQueryParameters } = ( - validateRequests + const requestValidator = new middlewares.RequestValidator( + apiDoc, + this.ajvOpts.request, ); - const requestValidator = new middlewares.RequestValidator(apiDoc, { - nullable: true, - coerceTypes, - removeAdditional: false, - useDefaults: true, - unknownFormats, - allowUnknownQueryParameters, - format: validateFormats, - formats: formats.reduce((acc, f) => { - acc[f.name] = { - type: f.type, - validate: f.validate, - }; - return acc; - }, {}), - }); return (req, res, next) => requestValidator.validate(req, res, next); } private responseValidationMiddleware(apiDoc: OpenAPIV3.Document) { - const { - coerceTypes, - unknownFormats, - validateResponses, - validateFormats, - formats, - } = this.options; - const { removeAdditional } = validateResponses; - - return new middlewares.ResponseValidator(apiDoc, { - nullable: true, - coerceTypes, - removeAdditional, - unknownFormats, - format: validateFormats, - formats: formats.reduce((acc, f) => { - acc[f.name] = { - type: f.type, - valdiate: f.validate, - }; - return acc; - }, {}), - }).validate(); + return new middlewares.ResponseValidator( + apiDoc, + this.ajvOpts.response, + ).validate(); } installOperationHandlers(baseUrl: string, context: OpenApiContext): Router { @@ -411,3 +353,57 @@ export class OpenApiValidator { } } } + +class AjvOptions { + private options: OpenApiValidatorOpts; + constructor(options: OpenApiValidatorOpts) { + this.options = options; + } + get preprocessor(): ajv.Options { + return this.baseOptions(); + } + + get response(): ajv.Options { + const { removeAdditional } = ( + this.options.validateResponses + ); + return { + ...this.baseOptions(), + useDefaults: false, + removeAdditional, + }; + } + + get request(): RequestValidatorOptions { + const { allowUnknownQueryParameters } = ( + this.options.validateRequests + ); + return { + ...this.baseOptions(), + allowUnknownQueryParameters, + }; + } + + get multipart(): ajv.Options { + return this.baseOptions(); + } + + private baseOptions(): ajv.Options { + const { coerceTypes, unknownFormats, validateFormats } = this.options; + return { + nullable: true, + coerceTypes, + useDefaults: true, + removeAdditional: false, + unknownFormats, + format: validateFormats, + formats: this.options.formats.reduce((acc, f) => { + acc[f.name] = { + type: f.type, + validate: f.validate, + }; + return acc; + }, {}), + }; + } +}