117 lines
3.9 KiB
JavaScript
117 lines
3.9 KiB
JavaScript
const Ajv = require('ajv');
|
|
const ParserError = require('./errors/parser-error');
|
|
const asyncapi = require('@asyncapi/specs');
|
|
const { improveAjvErrors } = require('./utils');
|
|
const cloneDeep = require('lodash.clonedeep');
|
|
|
|
const ajv = new Ajv({
|
|
jsonPointers: true,
|
|
allErrors: true,
|
|
schemaId: 'auto',
|
|
logger: false,
|
|
});
|
|
ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-04.json'));
|
|
|
|
module.exports = {
|
|
parse,
|
|
getMimeTypes
|
|
};
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
async function parse({ message, originalAsyncAPIDocument, fileFormat, parsedAsyncAPIDocument, pathToPayload, defaultSchemaFormat }) {
|
|
const payload = message.payload;
|
|
if (!payload) return;
|
|
|
|
message['x-parser-original-schema-format'] = message.schemaFormat || defaultSchemaFormat;
|
|
message['x-parser-original-payload'] = cloneDeep(message.payload);
|
|
|
|
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(errors, pathToPayload), originalAsyncAPIDocument, fileFormat),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
function getMimeTypes() {
|
|
const mimeTypes = [
|
|
'application/schema;version=draft-07',
|
|
'application/schema+json;version=draft-07',
|
|
'application/schema+yaml;version=draft-07',
|
|
];
|
|
['2.0.0', '2.1.0', '2.2.0', '2.3.0', '2.4.0', '2.5.0', '2.6.0'].forEach(version => {
|
|
mimeTypes.push(
|
|
`application/vnd.aai.asyncapi;version=${version}`,
|
|
`application/vnd.aai.asyncapi+json;version=${version}`,
|
|
`application/vnd.aai.asyncapi+yaml;version=${version}`,
|
|
);
|
|
});
|
|
return mimeTypes;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
const payloadSchema = preparePayloadSchema(asyncapi[String(version)], version);
|
|
ajv.addSchema(payloadSchema, 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} version AsyncAPI version.
|
|
* @returns {Object} valid JSON Schema document describing format of AsyncAPI-valid schema for message payload
|
|
*/
|
|
function preparePayloadSchema(asyncapiSchema, version) {
|
|
const payloadSchema = `http://asyncapi.com/definitions/${version}/schema.json`;
|
|
const definitions = asyncapiSchema.definitions;
|
|
// Remove the meta schemas because it is already present within Ajv, and it's not possible to add duplicate schemas.
|
|
delete definitions['http://json-schema.org/draft-07/schema'];
|
|
delete definitions['http://json-schema.org/draft-04/schema'];
|
|
return {
|
|
$ref: payloadSchema,
|
|
definitions
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Errors from Ajv contain dataPath information about parameter relative to parsed payload message.
|
|
* This function enriches dataPath with additional information on where is the parameter located in AsyncAPI document
|
|
*
|
|
* @private
|
|
* @param {Array<Object>} errors Ajv errors
|
|
* @param {String} path Path to location of the payload schema in AsyncAPI Document
|
|
* @returns {Array<Object>} same object as received in input but with modified datePath property so it contain full path relative to AsyncAPI document
|
|
*/
|
|
function addFullPathToDataPath(errors, path) {
|
|
return errors.map((err) => ({
|
|
...err,
|
|
...{
|
|
dataPath: `${path}${err.dataPath}`
|
|
}
|
|
}));
|
|
}
|
|
|