Skip to content

Commit

Permalink
fix: #1881 fixed memory leak for validation
Browse files Browse the repository at this point in the history
  • Loading branch information
Łukasz Kużyński committed Sep 20, 2021
1 parent 1c62191 commit bfc258a
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 8 deletions.
8 changes: 7 additions & 1 deletion packages/http/src/validator/validators/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export type Deps<Target> = {
defaultStyle: HttpParamStyles;
};

const schemaCache = new WeakMap<IHttpParam[], JSONSchema>();

export const validateParams = <Target>(
target: Target,
specs: IHttpParam[],
Expand All @@ -38,7 +40,11 @@ export const validateParams = <Target>(
return pipe(
NEA.fromArray(specs),
O.map(specs => {
const schema = createJsonSchemaFromParams(specs);
const schema = schemaCache.get(specs) ?? createJsonSchemaFromParams(specs);
if (!schemaCache.has(specs)) {
schemaCache.set(specs, schema);
}

const parameterValues = pickBy(
mapValues(
keyBy(specs, s => s.name.toLowerCase()),
Expand Down
35 changes: 28 additions & 7 deletions packages/http/src/validator/validators/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DiagnosticSeverity } from '@stoplight/types';
import * as O from 'fp-ts/Option';
import { pipe } from 'fp-ts/function';
import { NonEmptyArray, fromArray, map } from 'fp-ts/NonEmptyArray';
import Ajv, { ErrorObject, Logger, Options } from 'ajv';
import Ajv, { ErrorObject, Logger, Options, ValidateFunction } from 'ajv';
import type AjvCore from 'ajv/dist/core';
import Ajv2019 from 'ajv/dist/2019';
import Ajv2020 from 'ajv/dist/2020';
Expand Down Expand Up @@ -89,6 +89,32 @@ export const convertAjvErrors = (errors: NonEmptyArray<ErrorObject>, severity: D
})
);

const validationsFunctionsCache = new WeakMap<JSONSchema, WeakMap<object, ValidateFunction>>();
const EMPTY_BUNDLE = { _emptyBundle: true };

function getValidationFunction(ajvInstance: AjvCore, schema: JSONSchema, bundle?: unknown): ValidateFunction {
const bundledFunctionsCache = validationsFunctionsCache.get(schema);
const bundleKey = typeof bundle === 'object' && bundle !== null ? bundle : EMPTY_BUNDLE;
if (bundledFunctionsCache) {
const validationFunction = bundledFunctionsCache.get(bundleKey);
if (validationFunction) {
return validationFunction;
}
}

const validationFunction = ajvInstance.compile({
...schema,
__bundled__: bundle,
});

if (!bundledFunctionsCache) {
validationsFunctionsCache.set(schema, new WeakMap());
}

validationsFunctionsCache.get(schema)!.set(bundleKey, validationFunction);
return validationFunction;
}

export const validateAgainstSchema = (
value: unknown,
schema: JSONSchema,
Expand All @@ -97,12 +123,7 @@ export const validateAgainstSchema = (
bundle?: unknown
): O.Option<NonEmptyArray<IPrismDiagnostic>> =>
pipe(
O.tryCatch(() =>
assignAjvInstance(String(schema.$schema), coerce).compile({
...schema,
__bundled__: bundle,
})
),
O.tryCatch(() => getValidationFunction(assignAjvInstance(String(schema.$schema), coerce), schema, bundle)),
O.chainFirst(validateFn => O.tryCatch(() => validateFn(value))),
O.chain(validateFn => pipe(O.fromNullable(validateFn.errors), O.chain(fromArray))),
O.map(errors => convertAjvErrors(errors, DiagnosticSeverity.Error, prefix))
Expand Down

0 comments on commit bfc258a

Please sign in to comment.