@asyncapi/parser
Advanced tools
Comparing version 0.24.0 to 0.25.0
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 | ||
}; |
@@ -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 | ||
} | ||
} | ||
]); | ||
} | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
333446
5695