Skip to content

Commit

Permalink
fix: memory leak of AJV (asyncapi#406)
Browse files Browse the repository at this point in the history
  • Loading branch information
magicmatatjahu authored Nov 19, 2021
1 parent c7352b6 commit a4b11df
Show file tree
Hide file tree
Showing 4 changed files with 23,220 additions and 22 deletions.
38 changes: 28 additions & 10 deletions lib/asyncapiSchemaFormatParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ const asyncapi = require('@asyncapi/specs');
const { improveAjvErrors } = require('./utils');
const cloneDeep = require('lodash.clonedeep');

const ajv = new Ajv({
jsonPointers: true,
allErrors: true,
schemaId: 'id',
logger: false,
});
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));

module.exports = {
parse,
getMimeTypes
Expand All @@ -19,21 +27,15 @@ async function parse({ message, originalAsyncAPIDocument, fileFormat, parsedAsyn
message['x-parser-original-schema-format'] = message.schemaFormat || defaultSchemaFormat;
message['x-parser-original-payload'] = cloneDeep(message.payload);

const ajv = new Ajv({
jsonPointers: true,
allErrors: true,
schemaId: 'id',
logger: false,
});
const payloadSchema = preparePayloadSchema(asyncapi[parsedAsyncAPIDocument.asyncapi]);
const validate = ajv.compile(payloadSchema);
const validate = getValidator(parsedAsyncAPIDocument.asyncapi);
const valid = validate(payload);
const errors = validate.errors && [...validate.errors];

if (!valid) throw new ParserError({
type: 'schema-validation-errors',
title: 'This is not a valid AsyncAPI Schema Object.',
parsedJSON: parsedAsyncAPIDocument,
validationErrors: improveAjvErrors(addFullPathToDataPath(validate.errors, pathToPayload), originalAsyncAPIDocument, fileFormat),
validationErrors: improveAjvErrors(addFullPathToDataPath(errors, pathToPayload), originalAsyncAPIDocument, fileFormat),
});
}

Expand All @@ -57,12 +59,28 @@ function getMimeTypes() {
];
}

/**
* Creates (or reuses) a function that validates an AsyncAPI Schema Object based on the passed AsyncAPI version.
*
* @private
* @param {Object} version AsyncAPI version.
* @returns {Function} Function that validates an AsyncAPI Schema Object based on the passed AsyncAPI version.
*/
function getValidator(version) {
let validate = ajv.getSchema(version);
if (!validate) {
ajv.addSchema(preparePayloadSchema(asyncapi[String(version)]), version);
validate = ajv.getSchema(version);
}
return validate;
}

/**
* To validate schema of the payload we just need a small portion of official AsyncAPI spec JSON Schema, the definition of the schema must be
* a main part of the JSON Schema
*
* @private
* @param {Object} asyncapiSchema AsyncAPI specification JSON Schema
* @param {Object} asyncapiSchema AsyncAPI specification JSON Schema
* @returns {Object} valid JSON Schema document describing format of AsyncAPI-valid schema for message payload
*/
function preparePayloadSchema(asyncapiSchema) {
Expand Down
41 changes: 29 additions & 12 deletions lib/parser.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const path = require('path');
const Ajv = require('ajv');
const fetch = require('node-fetch');
const Ajv = require('ajv');
const asyncapi = require('@asyncapi/specs');
const $RefParser = require('@apidevtools/json-schema-ref-parser');
const mergePatch = require('tiny-merge-patch').apply;
Expand All @@ -16,6 +16,14 @@ const PARSERS = {};
const xParserCircle = 'x-parser-circular';
const xParserMessageParsed = 'x-parser-message-parsed';

const ajv = new Ajv({
jsonPointers: true,
allErrors: true,
schemaId: 'id',
logger: false,
});
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));

/**
* @module @asyncapi/parser
*/
Expand Down Expand Up @@ -86,22 +94,15 @@ async function parse(asyncapiYAMLorJSON, options = {}) {
//later we perform full dereference of circular refs if they occure
await dereference(refParser, parsedJSON, initialFormat, asyncapiYAMLorJSON, { ...options, dereference: { circular: 'ignore' } });

const ajv = new Ajv({
jsonPointers: true,
allErrors: true,
schemaId: 'id',
logger: false,
});

ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));

const validate = ajv.compile(asyncapi[parsedJSON.asyncapi]);
const validate = getValidator(parsedJSON.asyncapi);
const valid = validate(parsedJSON);
const errors = validate.errors && [...validate.errors];

if (!valid) throw new ParserError({
type: 'validation-errors',
title: 'There were errors validating the AsyncAPI document.',
parsedJSON,
validationErrors: improveAjvErrors(validate.errors, asyncapiYAMLorJSON, initialFormat),
validationErrors: improveAjvErrors(errors, asyncapiYAMLorJSON, initialFormat),
});

await customDocumentOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options);
Expand Down Expand Up @@ -175,6 +176,22 @@ async function handleCircularRefs(refParser, parsedJSON, initialFormat, asyncapi
parsedJSON[String(xParserCircle)] = true;
}

/**
* Creates (or reuses) a function that validates an AsyncAPI document based on the passed AsyncAPI version.
*
* @private
* @param {Object} version AsyncAPI version.
* @returns {Function} Function that validates an AsyncAPI document based on the passed AsyncAPI version.
*/
function getValidator(version) {
let validate = ajv.getSchema(version);
if (!validate) {
ajv.addSchema(asyncapi[String(version)], version);
validate = ajv.getSchema(version);
}
return validate;
}

async function customDocumentOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options) {
validateServerVariables(parsedJSON, asyncapiYAMLorJSON, initialFormat);
validateServerSecurity(parsedJSON, asyncapiYAMLorJSON, initialFormat, SPECIAL_SECURITY_TYPES);
Expand Down
Loading

0 comments on commit a4b11df

Please sign in to comment.