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 34cf86ce..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,14 +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, - }).preProcess(); + new RequestSchemaPreprocessor( + spec.apiDoc, + this.ajvOpts.preprocessor, + ).preProcess(); return { context: new OpenApiContext(spec, this.options.ignorePaths), @@ -249,7 +249,7 @@ export class OpenApiValidator { private multipartMiddleware(apiDoc: OpenAPIV3.Document) { return middlewares.multipart(apiDoc, { multerOpts: this.options.fileUploader, - unknownFormats: this.options.unknownFormats, + ajvOpts: this.ajvOpts.multipart, }); } @@ -261,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 { @@ -394,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; + }, {}), + }; + } +}