Skip to content

Commit 990995b

Browse files
0bex0rbruggem
andauthored
Optimise Ajv cache usage (cdimascio#1062)
* Add cache usage * fix tests * change comment * Update src/middlewares/openapi.request.validator.ts Co-authored-by: Roberto Bruggemann <[email protected]> --------- Co-authored-by: Roberto Bruggemann <[email protected]>
1 parent 218a4b1 commit 990995b

File tree

3 files changed

+30
-10
lines changed

3 files changed

+30
-10
lines changed

src/middlewares/openapi.request.validator.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
ContentType,
2222
ajvErrorsToValidatorError,
2323
augmentAjvErrors,
24+
useAjvCache,
2425
} from './util';
2526

2627
type OperationObject = OpenAPIV3.OperationObject;
@@ -29,7 +30,6 @@ type ReferenceObject = OpenAPIV3.ReferenceObject;
2930
type SecurityRequirementObject = OpenAPIV3.SecurityRequirementObject;
3031
type SecuritySchemeObject = OpenAPIV3.SecuritySchemeObject;
3132
type ApiKeySecurityScheme = OpenAPIV3.ApiKeySecurityScheme;
32-
3333
export class RequestValidator {
3434
private middlewareCache: { [key: string]: RequestHandler } = {};
3535
private apiDoc: OpenAPIV3.DocumentV3 | OpenAPIV3.DocumentV3_1;
@@ -80,7 +80,7 @@ export class RequestValidator {
8080
const key = `${req.method}-${path}-${contentTypeKey}`;
8181

8282
if (!this.middlewareCache[key]) {
83-
const middleware = this.buildMiddleware(path, reqSchema, contentType);
83+
const middleware = this.buildMiddleware(path, reqSchema, contentType, key);
8484
this.middlewareCache[key] = middleware;
8585
}
8686
return this.middlewareCache[key](req, res, next);
@@ -104,6 +104,7 @@ export class RequestValidator {
104104
path: string,
105105
reqSchema: OperationObject,
106106
contentType: ContentType,
107+
ajvCacheKey: string
107108
): RequestHandler {
108109
const apiDoc = this.apiDoc;
109110
const schemaParser = new ParametersSchemaParser(this.ajv, apiDoc);
@@ -113,7 +114,7 @@ export class RequestValidator {
113114
const validator = new Validator(this.apiDoc, parameters, body, {
114115
general: this.ajv,
115116
body: this.ajvBody,
116-
});
117+
}, ajvCacheKey);
117118

118119
const allowUnknownQueryParameters = !!(
119120
reqSchema['x-eov-allow-unknown-query-parameters'] ??
@@ -330,6 +331,7 @@ class Validator {
330331
general: Ajv;
331332
body: Ajv;
332333
},
334+
ajvCacheKey: string
333335
) {
334336
this.apiDoc = apiDoc;
335337
this.schemaGeneral = this._schemaGeneral(parametersSchema);
@@ -338,8 +340,8 @@ class Validator {
338340
...(<any>this.schemaGeneral).properties, // query, header, params props
339341
body: (<any>this.schemaBody).properties.body, // body props
340342
};
341-
this.validatorGeneral = ajv.general.compile(this.schemaGeneral);
342-
this.validatorBody = ajv.body.compile(this.schemaBody);
343+
this.validatorGeneral = useAjvCache(ajv.general, this.schemaGeneral, ajvCacheKey);
344+
this.validatorBody = useAjvCache(ajv.body, this.schemaBody, ajvCacheKey);
343345
}
344346

345347
private _schemaGeneral(parameters: ParametersSchema): object {

src/middlewares/openapi.response.validator.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
augmentAjvErrors,
77
ContentType,
88
ajvErrorsToValidatorError,
9-
findResponseContent,
9+
findResponseContent, useAjvCache,
1010
} from './util';
1111
import {
1212
OpenAPIV3,
@@ -109,7 +109,7 @@ export class ResponseValidator {
109109

110110
let validators = this.validatorsCache[key];
111111
if (!validators) {
112-
validators = this.buildValidators(responses);
112+
validators = this.buildValidators(responses, key);
113113
this.validatorsCache[key] = validators;
114114
}
115115
return validators;
@@ -212,7 +212,7 @@ export class ResponseValidator {
212212
* @param responses
213213
* @returns a map of validators
214214
*/
215-
private buildValidators(responses: OpenAPIV3.ResponsesObject): {
215+
private buildValidators(responses: OpenAPIV3.ResponsesObject, ajvCacheKey: string): {
216216
[key: string]: ValidateFunction;
217217
} {
218218
const validationTypes = (response) => {
@@ -295,9 +295,10 @@ export class ResponseValidator {
295295
const schema = contentTypeSchemas[contentType];
296296
schema.paths = this.spec.paths; // add paths for resolution with multi-file
297297
schema.components = this.spec.components; // add components for resolution w/ multi-file
298+
const validator = useAjvCache(this.ajvBody, <object>schema, `${ajvCacheKey}-${code}-${contentType}`)
298299
validators[code] = {
299300
...validators[code],
300-
[contentType]: this.ajvBody.compile(<object>schema),
301+
[contentType]: validator,
301302
};
302303
}
303304
}

src/middlewares/util.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { ErrorObject } from 'ajv-draft-04';
22
import { Request } from 'express';
3-
import { ValidationError } from '../framework/types';
3+
import { AjvInstance, ValidationError } from '../framework/types';
44

55
export class ContentType {
66
public readonly mediaType: string = null;
@@ -174,3 +174,20 @@ export const zipObject = (keys, values) =>
174174
return acc
175175
}, {})
176176

177+
/**
178+
* Tries to fetch a schema from ajv instance by the provided key otherwise adds (and
179+
* compiles) the schema under provided key. We provide a key to avoid ajv library
180+
* using the whole schema as a cache key, leading to a lot of unnecessary memory
181+
* usage - this is not recommended by the library either:
182+
* https://ajv.js.org/guide/managing-schemas.html#cache-key-schema-vs-key-vs-id
183+
*
184+
* @param ajvCacheKey - Key which will be used for ajv cache
185+
*/
186+
export function useAjvCache(ajv: AjvInstance, schema: object, ajvCacheKey: string) {
187+
let validator = ajv.getSchema(ajvCacheKey);
188+
if (!validator) {
189+
ajv.addSchema(schema, ajvCacheKey);
190+
validator = ajv.getSchema(ajvCacheKey);
191+
}
192+
return validator
193+
}

0 commit comments

Comments
 (0)