Befor generating

This commit is contained in:
marys
2026-06-01 13:17:37 +02:00
parent 3383f4bf4a
commit 1aa1b5f625
6756 changed files with 649946 additions and 1 deletions
+339
View File
@@ -0,0 +1,339 @@
const YAML = require('js-yaml');
const { yamlAST, loc } = require('@fmvilas/pseudo-yaml-ast');
const jsonAST = require('json-to-ast');
const jsonParseBetterErrors = require('../lib/json-parse');
const ParserError = require('./errors/parser-error');
const jsonPointerToArray = jsonPointer => (jsonPointer || '/').split('/').splice(1);
const utils = module.exports;
const getAST = (asyncapiYAMLorJSON, initialFormat) => {
if (initialFormat === 'yaml') {
return yamlAST(asyncapiYAMLorJSON);
} else if (initialFormat === 'json') {
return jsonAST(asyncapiYAMLorJSON);
}
};
const findNode = (obj, location) => {
for (const key of location) {
obj = obj ? obj[utils.untilde(key)] : null;
}
return obj;
};
const findNodeInAST = (ast, location) => {
let obj = ast;
for (const key of location) {
if (!Array.isArray(obj.children)) return;
let childArray;
const child = obj.children.find(c => {
if (!c) return;
if (c.type === 'Object') return childArray = c.children.find(a => a.key.value === utils.untilde(key));
return c.type === 'Property' && c.key && c.key.value === utils.untilde(key);
});
if (!child) return;
obj = childArray ? childArray.value : child.value;
}
return obj;
};
const findLocationOf = (keys, ast, initialFormat) => {
if (initialFormat === 'js') return { jsonPointer: `/${keys.join('/')}` };
let node;
if (initialFormat === 'yaml') {
node = findNode(ast, keys);
} else if (initialFormat === 'json') {
node = findNodeInAST(ast, keys);
}
if (!node) return { jsonPointer: `/${keys.join('/')}` };
let info;
if (initialFormat === 'yaml') {
// disable eslint because loc is a Symbol
// eslint-disable-next-line security/detect-object-injection
info = node[loc];
} else if (initialFormat === 'json') {
info = node.loc;
}
if (!info) return { jsonPointer: `/${keys.join('/')}` };
return {
jsonPointer: `/${keys.join('/')}`,
startLine: info.start.line,
startColumn: info.start.column + 1,
startOffset: info.start.offset,
endLine: info.end ? info.end.line : undefined,
endColumn: info.end ? info.end.column + 1 : undefined,
endOffset: info.end ? info.end.offset : undefined,
};
};
utils.tilde = (str) => {
return str.replace(/[~\/]{1}/g, (m) => {
switch (m) {
case '/': return '~1';
case '~': return '~0';
}
return m;
});
};
utils.untilde = (str) => {
if (!str.includes('~')) return str;
return str.replace(/~[01]/g, (m) => {
switch (m) {
case '~1': return '/';
case '~0': return '~';
}
return m;
});
};
utils.toJS = (asyncapiYAMLorJSON) => {
if (!asyncapiYAMLorJSON) {
throw new ParserError({
type: 'null-or-falsey-document',
title: 'Document can\'t be null or falsey.',
});
}
if (asyncapiYAMLorJSON.constructor && asyncapiYAMLorJSON.constructor.name === 'Object') {
return {
initialFormat: 'js',
parsedJSON: asyncapiYAMLorJSON,
};
}
if (typeof asyncapiYAMLorJSON !== 'string') {
throw new ParserError({
type: 'invalid-document-type',
title: 'The AsyncAPI document has to be either a string or a JS object.',
});
}
if (asyncapiYAMLorJSON.trimLeft().startsWith('{')) {
try {
return {
initialFormat: 'json',
parsedJSON: jsonParseBetterErrors(asyncapiYAMLorJSON),
};
} catch (e) {
throw new ParserError({
type: 'invalid-json',
title: 'The provided JSON is not valid.',
detail: e.message,
location: {
startOffset: e.offset,
startLine: e.startLine,
startColumn: e.startColumn,
},
});
}
} else {
try {
return {
initialFormat: 'yaml',
parsedJSON: YAML.safeLoad(asyncapiYAMLorJSON),
};
} catch (err) {
throw new ParserError({
type: 'invalid-yaml',
title: 'The provided YAML is not valid.',
detail: err.message,
location: {
startOffset: err.mark.position,
startLine: err.mark.line + 1,
startColumn: err.mark.column + 1,
},
});
}
}
};
utils.findRefs = (errors, initialFormat, asyncapiYAMLorJSON) => {
let refs = [];
errors.map(({ path }) => refs.push({ location: [...path.map(utils.tilde), '$ref'] }));
if (initialFormat === 'js') {
return refs.map(ref => ({ jsonPointer: `/${ref.location.join('/')}` }));
}
if (initialFormat === 'yaml') {
const pseudoAST = yamlAST(asyncapiYAMLorJSON);
refs = refs.map(ref => findLocationOf(ref.location, pseudoAST, initialFormat));
} else if (initialFormat === 'json') {
const ast = jsonAST(asyncapiYAMLorJSON);
refs = refs.map(ref => findLocationOf(ref.location, ast, initialFormat));
}
return refs;
};
utils.getLocationOf = (jsonPointer, asyncapiYAMLorJSON, initialFormat) => {
const ast = getAST(asyncapiYAMLorJSON, initialFormat);
if (!ast) return { jsonPointer };
return findLocationOf(jsonPointerToArray(jsonPointer), ast, initialFormat);
};
utils.improveAjvErrors = (errors, asyncapiYAMLorJSON, initialFormat) => {
const ast = getAST(asyncapiYAMLorJSON, initialFormat);
return errors.map(error => {
const defaultLocation = { jsonPointer: error.dataPath || '/' };
const additionalProperty = error.params.additionalProperty;
const jsonPointer = additionalProperty ? `${error.dataPath}/${additionalProperty}`: error.dataPath;
return {
title: `${error.dataPath || '/'} ${error.message}`,
location: ast ? findLocationOf(jsonPointerToArray(jsonPointer), ast, initialFormat) : defaultLocation,
};
});
};
/**
* It parses the string and returns an array with all values that are between curly braces, including braces
* @function parseUrlVariables
* @private
*/
utils.parseUrlVariables = str => {
if (typeof str !== 'string') return;
return str.match(/{(.+?)}/g);
};
/**
* It parses the string and returns url parameters as string
* @function parseUrlQueryParameters
* @private
*/
utils.parseUrlQueryParameters = str => {
if (typeof str !== 'string') return;
return str.match(/\?((.*=.*)(&?))/g);
};
/**
* Returns base URL parsed from location of AsyncAPI document
*
* @function getBaseUrl
* @private
* @param {String} url URL of AsyncAPI document
*/
utils.getBaseUrl = url => {
url = typeof url !== 'string' ? String(url) : url;
//URL validation is not performed because 'node-fetch' performs its own
//validation at fetch time, so no repetition of this task is made.
//Only ensuring that 'url' has type of 'string' and letting 'node-fetch' deal
//with the rest.
return url.substring(0, url.lastIndexOf('/') + 1);
};
/**
* Returns an array of not existing properties in provided object with names specified in provided array
* @function getMissingProps
* @private
*/
utils.getMissingProps = (arr, obj) => {
arr = arr.map(val => val.replace(/[{}]/g, ''));
if (!obj) return arr;
return arr.filter(val => {
return !obj.hasOwnProperty(val);
});
};
/**
* Returns array of errors messages compatible with validationErrors parameter from ParserError
*
* @function groupValidationErrors
* @private
* @param {String} root name of the root element in the AsyncAPI document, for example channels
* @param {String} errorMessage the text of the custom error message that will follow the path that points the error
* @param {Map} errorElements map of error elements cause the validation error might happen in many places in the document.
* The key should have a path information where the error was found, the value holds information about error element but it is not mandatory
* @param {String} asyncapiYAMLorJSON AsyncAPI document in string
* @param {String} initialFormat information of the document was oryginally JSON or YAML
* @returns {Array<Object>} Object has always 2 keys, title and location. Title is a combination of errorElement key + errorMessage + errorElement value.
* Location is the object with information about location of the issue in the file and json Pointer
*/
utils.groupValidationErrors = (root, errorMessage, errorElements, asyncapiYAMLorJSON, initialFormat) => {
const errors = [];
errorElements.forEach((val, key) => {
if (typeof val === 'string') val = utils.untilde(val);
const jsonPointer = root ? `/${root}/${key}` : `/${key}`;
errors.push({
title: val ? `${ utils.untilde(key) } ${errorMessage}: ${val}` : `${ utils.untilde(key) } ${errorMessage}`,
location: utils.getLocationOf(jsonPointer, asyncapiYAMLorJSON, initialFormat)
});
});
return errors;
};
/**
* extend map with channel params missing corresponding param object
*
* @function setNotProvidedParams
* @private
* @param {Array<String>} variables array of all identified URL variables in a channel name
* @param {Object} val the channel object for which to identify the missing parameters
* @param {String} key the channel name.
* @param {Array<Object>} notProvidedChannelParams concatinated list of missing parameters for all channels
* @param {Map} notProvidedParams result map of all missing parameters extended by this function
*/
utils.setNotProvidedParams = (variables, val, key, notProvidedChannelParams, notProvidedParams) => {
const missingChannelParams = utils.getMissingProps(variables, val.parameters);
if (missingChannelParams.length) {
notProvidedParams.set(utils.tilde(key),
notProvidedChannelParams
? notProvidedChannelParams.concat(missingChannelParams)
: missingChannelParams);
}
};
/**
* Returns an array of server names listed in a channel's servers list that are not declared in the top-level servers object.
*
* @param {Map} parsedJSON the parsed AsyncAPI document, with potentially a top-level map of servers (keys are server names)
* @param {Object} channel the channel object for which to validate the servers list (array elements are server names)
* @private
*/
utils.getUnknownServers = (parsedJSON, channel) => {
// servers list on channel
if (!channel) return []; // no channel: no unknown servers
const channelServers = channel.servers;
if (!channelServers || channelServers.length === 0) return []; // no servers listed on channel: no unknown servers
// top-level servers map
const servers = parsedJSON.servers;
if (!servers) return channelServers; // servers list on channel but no top-level servers: all servers are unknown
const serversMap = new Map(Object.entries(servers));
// retain only servers listed on channel that are not defined in the top-level servers map
return channelServers.filter(serverName => {
return !serversMap.has(serverName);
});
};
/**
* returns default schema format for a given asyncapi version
*
* @function getDefaultSchemaFormat
* @private
* @param {String} asyncapiVersion AsyncAPI spec version.
*/
utils.getDefaultSchemaFormat = (asyncapiVersion) => {
return `application/vnd.aai.asyncapi;version=${asyncapiVersion}`;
};