Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@asyncapi/parser

Package Overview
Dependencies
Maintainers
3
Versions
170
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@asyncapi/parser - npm Package Compare versions

Comparing version 0.24.0 to 0.25.0

92

lib/customValidators.js
const ParserError = require('./errors/parser-error');
const { parseUrlVariables, getMissingProps, groupValidationErrors } = require('./utils');
const { parseUrlVariables, getMissingProps, groupValidationErrors, tilde } = require('./utils');
const validationError = 'validation-errors';
/**
* Validates if variables provided in the url have corresponding variable object defined
*
* @param {Object} parsedJSON parsed AsyncAPI document
* @param {String} asyncapiYAMLorJSON AsyncAPI document in string
* @param {String} initialFormat information of the document was oryginally JSON or YAML
* @returns {Boolean} true in case the document is valid, otherwise throws ParserError
*/
function validateServerVariables(parsedJSON, asyncapiYAMLorJSON, initialFormat) {

@@ -13,3 +22,3 @@ const srvs = parsedJSON.servers;

const variables = parseUrlVariables(val.url);
const notProvidedServerVars = notProvidedVariables.get(key);
const notProvidedServerVars = notProvidedVariables.get(tilde(key));
if (!variables) return;

@@ -20,3 +29,3 @@

notProvidedVariables.set(key,
notProvidedVariables.set(tilde(key),
notProvidedServerVars

@@ -27,8 +36,8 @@ ? notProvidedServerVars.concat(missingServerVariables)

if (notProvidedVariables.size > 0) {
if (notProvidedVariables.size) {
throw new ParserError({
type: 'validation-errors',
type: validationError,
title: 'Not all server variables are described with variable object',
parsedJSON,
validationErrors: groupValidationErrors('/servers/', 'server does not have a corresponding variable object for', notProvidedVariables, asyncapiYAMLorJSON, initialFormat)
validationErrors: groupValidationErrors('servers', 'server does not have a corresponding variable object for', notProvidedVariables, asyncapiYAMLorJSON, initialFormat)
});

@@ -39,3 +48,11 @@ }

}
/**
* Validates if parameters specified in the channel have corresponding parameters object defined
*
* @param {Object} parsedJSON parsed AsyncAPI document
* @param {String} asyncapiYAMLorJSON AsyncAPI document in string
* @param {String} initialFormat information of the document was oryginally JSON or YAML
* @returns {Boolean} true in case the document is valid, otherwise throws ParserError
*/
function validateChannelParams(parsedJSON, asyncapiYAMLorJSON, initialFormat) {

@@ -50,3 +67,3 @@ const chnls = parsedJSON.channels;

const variables = parseUrlVariables(key);
const notProvidedChannelParams = notProvidedParams.get(key);
const notProvidedChannelParams = notProvidedParams.get(tilde(key));
if (!variables) return;

@@ -58,3 +75,3 @@

notProvidedParams.set(key,
notProvidedParams.set(tilde(key),
notProvidedChannelParams

@@ -65,8 +82,8 @@ ? notProvidedChannelParams.concat(missingChannelParams)

if (notProvidedParams.size > 0) {
if (notProvidedParams.size) {
throw new ParserError({
type: 'validation-errors',
type: validationError,
title: 'Not all channel parameters are described with parameter object',
parsedJSON,
validationErrors: groupValidationErrors('/channels/', 'channel does not have a corresponding parameter object for', notProvidedParams, asyncapiYAMLorJSON, initialFormat)
validationErrors: groupValidationErrors('channels', 'channel does not have a corresponding parameter object for', notProvidedParams, asyncapiYAMLorJSON, initialFormat)
});

@@ -78,5 +95,54 @@ }

/**
* Validates if operationIds are duplicated in the document
*
* @param {Object} parsedJSON parsed AsyncAPI document
* @param {String} asyncapiYAMLorJSON AsyncAPI document in string
* @param {String} initialFormat information of the document was oryginally JSON or YAML
* @returns {Boolean} true in case the document is valid, otherwise throws ParserError
*/
function validateOperationId(parsedJSON, asyncapiYAMLorJSON, initialFormat, operations) {
const chnls = parsedJSON.channels;
if (!chnls) return true;
const chnlsMap = new Map(Object.entries(chnls));
//it is a map of paths, the one that is a duplicate and the one that is duplicated
const duplicatedOperations = new Map();
//is is a 2-dimentional array that holds information with operationId value and its path
const allOperations = [];
const addDuplicateToMap = (op, channelName, opName) => {
const operationId = op.operationId;
if (!operationId) return;
const operationPath = `${ tilde(channelName) }/${ opName }/operationId`;
const isOperationIdDuplicated = allOperations.filter(v => v[0] === operationId);
if (!isOperationIdDuplicated.length) return allOperations.push([operationId, operationPath]);
//isOperationIdDuplicated always holds one record and it is an array of paths, the one that is a duplicate and the one that is duplicated
duplicatedOperations.set(operationPath, isOperationIdDuplicated[0][1]);
};
chnlsMap.forEach((chnlObj,chnlName) => {
operations.forEach(opName => {
const op = chnlObj[opName];
if (op) addDuplicateToMap(op, chnlName, opName);
});
});
if (duplicatedOperations.size) {
throw new ParserError({
type: validationError,
title: 'operationId must be unique across all the operations.',
parsedJSON,
validationErrors: groupValidationErrors('channels', 'is a duplicate of', duplicatedOperations, asyncapiYAMLorJSON, initialFormat)
});
}
return true;
}
module.exports = {
validateChannelParams,
validateServerVariables
validateServerVariables,
validateOperationId
};

3

lib/parser.js

@@ -8,3 +8,3 @@ const path = require('path');

const ParserError = require('./errors/parser-error');
const { validateChannelParams, validateServerVariables } = require('./customValidators.js');
const { validateChannelParams, validateServerVariables, validateOperationId } = require('./customValidators.js');
const { toJS, findRefs, getLocationOf, improveAjvErrors } = require('./utils');

@@ -157,2 +157,3 @@ const AsyncAPIDocument = require('./models/asyncapi');

validateChannelParams(js, asyncapiYAMLorJSON, initialFormat);
validateOperationId(js, asyncapiYAMLorJSON, initialFormat, OPERATIONS);

@@ -159,0 +160,0 @@ for (const channelName in js.channels) {

@@ -10,2 +10,4 @@ const YAML = require('js-yaml');

const utils = module.exports;
const getAST = (asyncapiYAMLorJSON, initialFormat) => {

@@ -19,26 +21,5 @@ if (initialFormat === 'yaml') {

const tilde = (str) => {
return str.replace(/[~\/]{1}/g, (m) => {
switch (m) {
case '/': return '~1';
case '~': return '~0';
}
return m;
});
};
const untilde = (str) => {
if (!str.includes('~')) return str;
return str.replace(/~[01]/g, (m) => {
switch (m) {
case '~1': return '/';
case '~0': return '~';
}
return m;
});
};
const findNode = (obj, location) => {
for (const key of location) {
obj = obj[untilde(key)];
obj = obj[utils.untilde(key)];
}

@@ -52,3 +33,3 @@ return obj;

if (!Array.isArray(obj.children)) return;
const child = obj.children.find(c => c && c.type === 'Property' && c.key && c.key.value === untilde(key));
const child = obj.children.find(c => c && c.type === 'Property' && c.key && c.key.value === utils.untilde(key));
if (!child) return;

@@ -101,4 +82,23 @@ obj = child.value;

const utils = module.exports;
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) => {

@@ -209,3 +209,3 @@ if (!asyncapiYAMLorJSON) {

if (key === '$ref' && possibleRefUrls.includes(value)) {
refs.push({ location: [...scope.map(tilde), '$ref'] });
refs.push({ location: [...scope.map(utils.tilde), '$ref'] });
}

@@ -274,11 +274,21 @@ });

* Returns array of errors messages compatible with validationErrors parameter from ParserError
*
* @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
* @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 = [];
const regex = new RE2(/\//g);
errorElements.forEach((val,key) => {
errorElements.forEach((val, key) => {
if (typeof val === 'string') val = utils.untilde(val);
errors.push({
title: `${key} ${errorMessage}: ${val}`,
location: utils.getLocationOf(root + key.replace(regex, '~1'), asyncapiYAMLorJSON, initialFormat)
title: `${ utils.untilde(key) } ${errorMessage}: ${val}`,
location: utils.getLocationOf(`/${root}/${key}`, asyncapiYAMLorJSON, initialFormat)
});

@@ -285,0 +295,0 @@ });

{
"name": "@asyncapi/parser",
"version": "0.24.0",
"version": "0.25.0",
"description": "JavaScript AsyncAPI parser.",

@@ -5,0 +5,0 @@ "main": "lib/index.js",

@@ -1,2 +0,2 @@

const {validateChannelParams, validateServerVariables} = require('../lib/customValidators.js');
const {validateChannelParams, validateServerVariables, validateOperationId} = require('../lib/customValidators.js');
const chai = require('chai');

@@ -304,2 +304,103 @@

});
});
describe('validateOperationId()', function() {
const operations = ['subscribe', 'publish'];
it('should successfully validate operationId', async function() {
const inputString = `{
"asyncapi": "2.0.0",
"info": {
"version": "1.0.0"
},
"channels": {
"test/1": {
"publish": {
"operationId": "test1"
}
},
"test/2": {
"subscribe": {
"operationId": "test2"
}
}
}
}`;
const parsedInput = JSON.parse(inputString);
expect(validateOperationId(parsedInput, inputString, input, operations)).to.equal(true);
});
it('should successfully validate if channel object not provided', function() {
const inputString = '{}';
const parsedInput = JSON.parse(inputString);
expect(validateOperationId(parsedInput, inputString, input, operations)).to.equal(true);
});
it('should throw error that operationIds are duplicated and that they duplicate', function() {
const inputString = `{
"asyncapi": "2.0.0",
"info": {
"version": "1.0.0"
},
"channels": {
"test/1": {
"publish": {
"operationId": "test"
}
},
"test/2": {
"subscribe": {
"operationId": "test"
}
},
"test/3": {
"subscribe": {
"operationId": "test"
}
},
"test/4": {
"subscribe": {
"operationId": "test4"
}
}
}
}`;
const parsedInput = JSON.parse(inputString);
try {
validateOperationId(parsedInput, inputString, input, operations);
} catch (e) {
expect(e.type).to.equal('https://github.com/asyncapi/parser-js/validation-errors');
expect(e.title).to.equal('operationId must be unique across all the operations.');
expect(e.parsedJSON).to.deep.equal(parsedInput);
expect(e.validationErrors).to.deep.equal([
{
title: 'test/2/subscribe/operationId is a duplicate of: test/1/publish/operationId',
location: {
jsonPointer: '/channels/test~12/subscribe/operationId',
startLine: 14,
startColumn: 29,
startOffset: 273,
endLine: 14,
endColumn: 35,
endOffset: 279
}
},
{
title: 'test/3/subscribe/operationId is a duplicate of: test/1/publish/operationId',
location: {
jsonPointer: '/channels/test~13/subscribe/operationId',
startLine: 19,
startColumn: 29,
startOffset: 375,
endLine: 19,
endColumn: 35,
endOffset: 381
}
}
]);
}
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc