Skip to content

Commit cf37281

Browse files
Guillaumecdimascio
Guillaume
andauthored
fix: request schema preprocessor and multipart middleware custom formats (#452)
* fix: request schema preprocessor unknown format error * fix: multipart middleware ajv options formats and types * common ajv options handler Co-authored-by: Carmine DiMascio <[email protected]>
1 parent bc30bb6 commit cf37281

File tree

3 files changed

+74
-62
lines changed

3 files changed

+74
-62
lines changed

src/framework/types.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ export type SecurityHandlers = {
3131
) => boolean | Promise<boolean>;
3232
};
3333

34-
export interface MultipartOpts extends ajv.Options {
35-
multerOpts: any;
34+
export interface MultipartOpts {
35+
multerOpts: boolean | multer.Options;
36+
ajvOpts: ajv.Options;
3637
}
3738

3839
export interface RequestValidatorOptions

src/middlewares/openapi.multipart.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ export function multipart(
1919
options: MultipartOpts,
2020
): OpenApiRequestHandler {
2121
const mult = multer(options.multerOpts);
22-
const Ajv = createRequestAjv(apiDoc, {
23-
unknownFormats: options.unknownFormats,
24-
});
22+
const Ajv = createRequestAjv(apiDoc, { ...options.ajvOpts });
2523
return (req, res, next) => {
2624
// TODO check that format: binary (for upload) else do not use multer.any()
2725
// use multer.none() if no binary parameters exist

src/openapi.validator.ts

+70-57
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ono from 'ono';
2+
import ajv = require('ajv');
23
import * as express from 'express';
34
import * as _uniq from 'lodash.uniq';
45
import * as cloneDeep from 'lodash.clonedeep';
@@ -15,6 +16,7 @@ import {
1516
OpenApiRequestMetadata,
1617
ValidateSecurityOpts,
1718
OpenAPIV3,
19+
RequestValidatorOptions,
1820
} from './framework/types';
1921
import { defaultResolver } from './resolvers';
2022
import { OperationHandlerOptions } from './framework/types';
@@ -35,6 +37,7 @@ export {
3537

3638
export class OpenApiValidator {
3739
readonly options: OpenApiValidatorOpts;
40+
readonly ajvOpts: AjvOptions;
3841

3942
constructor(options: OpenApiValidatorOpts) {
4043
this.validateOptions(options);
@@ -82,6 +85,7 @@ export class OpenApiValidator {
8285
}
8386

8487
this.options = options;
88+
this.ajvOpts = new AjvOptions(options);
8589
}
8690

8791
installMiddleware(spec: Promise<Spec>): OpenApiRequestHandler[] {
@@ -90,14 +94,10 @@ export class OpenApiValidator {
9094
const responseApiDoc = this.options.validateResponses
9195
? cloneDeep(spec.apiDoc)
9296
: null;
93-
new RequestSchemaPreprocessor(spec.apiDoc, {
94-
nullable: true,
95-
coerceTypes: this.options.coerceTypes,
96-
removeAdditional: false,
97-
useDefaults: true,
98-
unknownFormats: this.options.unknownFormats,
99-
format: this.options.validateFormats,
100-
}).preProcess();
97+
new RequestSchemaPreprocessor(
98+
spec.apiDoc,
99+
this.ajvOpts.preprocessor,
100+
).preProcess();
101101

102102
return {
103103
context: new OpenApiContext(spec, this.options.ignorePaths),
@@ -249,7 +249,7 @@ export class OpenApiValidator {
249249
private multipartMiddleware(apiDoc: OpenAPIV3.Document) {
250250
return middlewares.multipart(apiDoc, {
251251
multerOpts: this.options.fileUploader,
252-
unknownFormats: this.options.unknownFormats,
252+
ajvOpts: this.ajvOpts.multipart,
253253
});
254254
}
255255

@@ -261,59 +261,18 @@ export class OpenApiValidator {
261261
}
262262

263263
private requestValidationMiddleware(apiDoc: OpenAPIV3.Document) {
264-
const {
265-
coerceTypes,
266-
unknownFormats,
267-
validateRequests,
268-
validateFormats,
269-
formats,
270-
} = this.options;
271-
const { allowUnknownQueryParameters } = <ValidateRequestOpts>(
272-
validateRequests
264+
const requestValidator = new middlewares.RequestValidator(
265+
apiDoc,
266+
this.ajvOpts.request,
273267
);
274-
const requestValidator = new middlewares.RequestValidator(apiDoc, {
275-
nullable: true,
276-
coerceTypes,
277-
removeAdditional: false,
278-
useDefaults: true,
279-
unknownFormats,
280-
allowUnknownQueryParameters,
281-
format: validateFormats,
282-
formats: formats.reduce((acc, f) => {
283-
acc[f.name] = {
284-
type: f.type,
285-
validate: f.validate,
286-
};
287-
return acc;
288-
}, {}),
289-
});
290268
return (req, res, next) => requestValidator.validate(req, res, next);
291269
}
292270

293271
private responseValidationMiddleware(apiDoc: OpenAPIV3.Document) {
294-
const {
295-
coerceTypes,
296-
unknownFormats,
297-
validateResponses,
298-
validateFormats,
299-
formats,
300-
} = this.options;
301-
const { removeAdditional } = <ValidateResponseOpts>validateResponses;
302-
303-
return new middlewares.ResponseValidator(apiDoc, {
304-
nullable: true,
305-
coerceTypes,
306-
removeAdditional,
307-
unknownFormats,
308-
format: validateFormats,
309-
formats: formats.reduce((acc, f) => {
310-
acc[f.name] = {
311-
type: f.type,
312-
valdiate: f.validate,
313-
};
314-
return acc;
315-
}, {}),
316-
}).validate();
272+
return new middlewares.ResponseValidator(
273+
apiDoc,
274+
this.ajvOpts.response,
275+
).validate();
317276
}
318277

319278
installOperationHandlers(baseUrl: string, context: OpenApiContext): Router {
@@ -394,3 +353,57 @@ export class OpenApiValidator {
394353
}
395354
}
396355
}
356+
357+
class AjvOptions {
358+
private options: OpenApiValidatorOpts;
359+
constructor(options: OpenApiValidatorOpts) {
360+
this.options = options;
361+
}
362+
get preprocessor(): ajv.Options {
363+
return this.baseOptions();
364+
}
365+
366+
get response(): ajv.Options {
367+
const { removeAdditional } = <ValidateResponseOpts>(
368+
this.options.validateResponses
369+
);
370+
return {
371+
...this.baseOptions(),
372+
useDefaults: false,
373+
removeAdditional,
374+
};
375+
}
376+
377+
get request(): RequestValidatorOptions {
378+
const { allowUnknownQueryParameters } = <ValidateRequestOpts>(
379+
this.options.validateRequests
380+
);
381+
return {
382+
...this.baseOptions(),
383+
allowUnknownQueryParameters,
384+
};
385+
}
386+
387+
get multipart(): ajv.Options {
388+
return this.baseOptions();
389+
}
390+
391+
private baseOptions(): ajv.Options {
392+
const { coerceTypes, unknownFormats, validateFormats } = this.options;
393+
return {
394+
nullable: true,
395+
coerceTypes,
396+
useDefaults: true,
397+
removeAdditional: false,
398+
unknownFormats,
399+
format: validateFormats,
400+
formats: this.options.formats.reduce((acc, f) => {
401+
acc[f.name] = {
402+
type: f.type,
403+
validate: f.validate,
404+
};
405+
return acc;
406+
}, {}),
407+
};
408+
}
409+
}

0 commit comments

Comments
 (0)