@asyncapi/parser
Advanced tools
Comparing version 0.28.2 to 0.29.0
@@ -5,26 +5,7 @@ module.exports = (txt, reviver, context = 20) => { | ||
} catch (e) { | ||
if (typeof txt !== 'string') { | ||
const isEmptyArray = Array.isArray(txt) && txt.length === 0; | ||
const errorMessage = `Cannot parse ${ | ||
isEmptyArray ? 'an empty array' : String(txt)}`; | ||
throw new TypeError(errorMessage); | ||
} | ||
handleJsonNotString(txt); | ||
const syntaxErr = e.message.match(/^Unexpected token.*position\s+(\d+)/i); | ||
const errIdxBrokenJson = e.message.match(/^Unexpected end of JSON.*/i) ? txt.length - 1 : null; | ||
const errIdx = syntaxErr ? +syntaxErr[1] : errIdxBrokenJson; | ||
if (errIdx !== null) { | ||
const start = errIdx <= context | ||
? 0 | ||
: errIdx - context; | ||
const end = errIdx + context >= txt.length | ||
? txt.length | ||
: errIdx + context; | ||
e.message += ` while parsing near '${ | ||
start === 0 ? '' : '...' | ||
}${txt.slice(start, end)}${ | ||
end === txt.length ? '' : '...' | ||
}'`; | ||
} else { | ||
e.message += ` while parsing '${txt.slice(0, context * 2)}'`; | ||
} | ||
handleErrIdxNotNull(e, txt, errIdx, context); | ||
e.offset = errIdx; | ||
@@ -37,1 +18,28 @@ const lines = txt.substr(0, errIdx).split('\n'); | ||
}; | ||
function handleJsonNotString(txt) { | ||
if (typeof txt !== 'string') { | ||
const isEmptyArray = Array.isArray(txt) && txt.length === 0; | ||
const errorMessage = `Cannot parse ${ | ||
isEmptyArray ? 'an empty array' : String(txt)}`; | ||
throw new TypeError(errorMessage); | ||
} | ||
} | ||
function handleErrIdxNotNull(e, txt, errIdx, context) { | ||
if (errIdx !== null) { | ||
const start = errIdx <= context | ||
? 0 | ||
: errIdx - context; | ||
const end = errIdx + context >= txt.length | ||
? txt.length | ||
: errIdx + context; | ||
e.message += ` while parsing near '${ | ||
start === 0 ? '' : '...' | ||
}${txt.slice(start, end)}${ | ||
end === txt.length ? '' : '...' | ||
}'`; | ||
} else { | ||
e.message += ` while parsing '${txt.slice(0, context * 2)}'`; | ||
} | ||
} |
@@ -124,3 +124,8 @@ const path = require('path'); | ||
*/ | ||
function parseFromUrl(url, fetchOptions = {}, options) { | ||
function parseFromUrl(url, fetchOptions, options) { | ||
//Why not just addinga default to the arguments list? | ||
//All function parameters with default values should be declared after the function parameters without default values. Otherwise, it makes it impossible for callers to take advantage of defaults; they must re-specify the defaulted values or pass undefined in order to "get to" the non-default parameters. | ||
//To not break the API by changing argument position and to silet the linter it is just better to move adding | ||
if (!fetchOptions) fetchOptions = {}; | ||
return new Promise((resolve, reject) => { | ||
@@ -138,2 +143,3 @@ fetch(url, fetchOptions) | ||
return await refParser.dereference(options.path, parsedJSON, { | ||
continueOnError: true, | ||
parse: options.parse, | ||
@@ -146,5 +152,5 @@ resolve: options.resolve, | ||
type: 'dereference-error', | ||
title: err.message, | ||
title: err.errors[0].message, | ||
parsedJSON, | ||
refs: findRefs(parsedJSON, err.originalPath || err.path, options.path, initialFormat, asyncapiYAMLorJSON), | ||
refs: findRefs(err.errors, initialFormat, asyncapiYAMLorJSON), | ||
}); | ||
@@ -156,3 +162,3 @@ } | ||
* In case of circular refs, this function dereferences the spec again to dereference circular dependencies | ||
* Last step is to discover those and mark properly with information whey they are pointing | ||
* Special property is added to the document that indicates it contains circular refs | ||
*/ | ||
@@ -165,29 +171,12 @@ async function handleCircularRefs(parsedJSON, initialFormat, asyncapiYAMLorJSON, options) { | ||
async function customDocumentOperations(js, asyncapiYAMLorJSON, initialFormat, options) { | ||
validateServerVariables(js, asyncapiYAMLorJSON, initialFormat); | ||
validateServerSecurity(js, asyncapiYAMLorJSON, initialFormat, SPECIAL_SECURITY_TYPES); | ||
async function customDocumentOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options) { | ||
validateServerVariables(parsedJSON, asyncapiYAMLorJSON, initialFormat); | ||
validateServerSecurity(parsedJSON, asyncapiYAMLorJSON, initialFormat, SPECIAL_SECURITY_TYPES); | ||
if (!js.channels) return; | ||
if (!parsedJSON.channels) return; | ||
validateChannelParams(js, asyncapiYAMLorJSON, initialFormat); | ||
validateOperationId(js, asyncapiYAMLorJSON, initialFormat, OPERATIONS); | ||
validateChannelParams(parsedJSON, asyncapiYAMLorJSON, initialFormat); | ||
validateOperationId(parsedJSON, asyncapiYAMLorJSON, initialFormat, OPERATIONS); | ||
const promisesArray = []; | ||
Object.entries(js.channels).forEach(([channelName, channel]) => { | ||
promisesArray.push(...OPERATIONS.map(async (opName) => { | ||
const op = channel[String(opName)]; | ||
if (op) { | ||
const messages = op.message ? (op.message.oneOf || [op.message]) : []; | ||
if (options.applyTraits) { | ||
applyTraits(op); | ||
messages.forEach(m => applyTraits(m)); | ||
} | ||
const pathToPayload = `/channels/${channelName}/${opName}/message/payload`; | ||
for (const m of messages) { | ||
await validateAndConvertMessage(m, asyncapiYAMLorJSON, initialFormat, js, pathToPayload); | ||
} | ||
} | ||
})); | ||
}); | ||
await Promise.all(promisesArray); | ||
await customChannelsOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options); | ||
} | ||
@@ -214,3 +203,2 @@ | ||
* | ||
* @param {string[]} schemaFormats An array of schema formats the given schema parser is able to recognize and transform. | ||
* @param {Object} parserModule The schema parser module containing parse() and getMimeTypes() functions. | ||
@@ -244,1 +232,30 @@ */ | ||
} | ||
/** | ||
* Triggers additional operations on the AsyncAPI channels like traits application or message validation and conversion | ||
* | ||
* @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 | ||
* @param {Object} options Configuration options. | ||
*/ | ||
async function customChannelsOperations(parsedJSON, asyncapiYAMLorJSON, initialFormat, options) { | ||
const promisesArray = []; | ||
Object.entries(parsedJSON.channels).forEach(([channelName, channel]) => { | ||
promisesArray.push(...OPERATIONS.map(async (opName) => { | ||
const op = channel[String(opName)]; | ||
if (!op) return; | ||
const messages = op.message ? (op.message.oneOf || [op.message]) : []; | ||
if (options.applyTraits) { | ||
applyTraits(op); | ||
messages.forEach(m => applyTraits(m)); | ||
} | ||
const pathToPayload = `/channels/${channelName}/${opName}/message/payload`; | ||
for (const m of messages) { | ||
await validateAndConvertMessage(m, asyncapiYAMLorJSON, initialFormat, parsedJSON, pathToPayload); | ||
} | ||
})); | ||
}); | ||
await Promise.all(promisesArray); | ||
} |
@@ -79,11 +79,2 @@ const YAML = require('js-yaml'); | ||
const traverse = function (o, fn, scope = []) { | ||
Object.entries(o).forEach(([key,value]) => { | ||
fn.apply(this, [key, value, scope]); | ||
if (value !== null && typeof value === 'object') { | ||
traverse(value, fn, scope.concat(key)); | ||
} | ||
}); | ||
}; | ||
const getMapValue = (obj, key, Type) => { | ||
@@ -216,14 +207,7 @@ if (typeof key !== 'string' || !obj) return null; | ||
utils.findRefs = (json, absolutePath, relativePath, initialFormat, asyncapiYAMLorJSON) => { | ||
const possibleRefUrls = [absolutePath]; | ||
if (absolutePath.startsWith(relativePath)) possibleRefUrls.push(absolutePath.substr(relativePath.length)); | ||
utils.findRefs = (errors, initialFormat, asyncapiYAMLorJSON) => { | ||
let refs = []; | ||
traverse(json, (key, value, scope) => { | ||
if (key === '$ref' && possibleRefUrls.includes(value)) { | ||
refs.push({ location: [...scope.map(utils.tilde), '$ref'] }); | ||
} | ||
}); | ||
if (!refs.length) return refs; | ||
errors.map(({ path }) => refs.push({ location: [...path.map(utils.tilde), '$ref'] })); | ||
if (initialFormat === 'js') { | ||
@@ -230,0 +214,0 @@ return refs.map(ref => ({ jsonPointer: `/${ref.location.join('/')}` })); |
{ | ||
"name": "@asyncapi/parser", | ||
"version": "0.28.2", | ||
"version": "0.29.0", | ||
"description": "JavaScript AsyncAPI parser.", | ||
@@ -18,3 +18,3 @@ "main": "lib/index.js", | ||
"get-version": "echo $npm_package_version", | ||
"lint": "eslint --config .eslintrc ." | ||
"lint": "eslint --max-warnings 0 --config .eslintrc ." | ||
}, | ||
@@ -56,3 +56,3 @@ "bugs": { | ||
"dependencies": { | ||
"@apidevtools/json-schema-ref-parser": "github:fmvilas/json-schema-ref-parser#add-more-info-about-errors", | ||
"@apidevtools/json-schema-ref-parser": "^9.0.6", | ||
"@asyncapi/specs": "^2.7.1", | ||
@@ -59,0 +59,0 @@ "@fmvilas/pseudo-yaml-ast": "^0.3.1", |
@@ -21,28 +21,38 @@ const { EOL } = require('os'); | ||
const invalidJsonOutput = '{"asyncapi":"2.0.0","info":{"version":"1.0.0"},"channels":{"mychannel":{"publish":{"message":{"payload":{"type":"object","properties":{"name":{"type":"string"}}}}}}},"components":{"messages":{"testMessage":{"payload":{"type":"object","properties":{"name":{"type":"string"},"test":{"type":"object","properties":{"testing":{"type":"string"}}}}}}},"schemas":{"testSchema":{"type":"object","properties":{"name":{"type":"string"},"test":{"type":"object","properties":{"testing":{"type":"string"}}}}}}}}'; | ||
const outputJsonWithRefs = '{"asyncapi":"2.0.0","info":{"title":"My API","version":"1.0.0"},"channels":{"mychannel":{"publish":{"traits":[{"$ref":"#/components/operationTraits/docs"}],"externalDocs":{"x-extension":true,"url":"https://irrelevant.com"},"message":{"$ref":"#/components/messages/testMessage"}}},"oneOfMessageChannel":{"publish":{"message":{"oneOf":[{"$ref":"#/components/messages/testMessage"}]}}}},"components":{"messages":{"testMessage":{"traits":[{"$ref":"#/components/messageTraits/extension"}],"payload":{"$ref":"#/components/schemas/testSchema"}}},"schemas":{"testSchema":{"type":"object","properties":{"name":{"type":"string"},"test":{"$ref":"refs/refed.yaml"}}}},"messageTraits":{"extension":{"x-some-extension":"some extension"}},"operationTraits":{"docs":{"externalDocs":{"url":"https://company.com/docs"}}}}}'; | ||
const invalidAsyncAPI = { asyncapi: '2.0.0', info: {} }; | ||
const outputJsonWithRefs = '{"asyncapi":"2.0.0","info":{"title":"My API","version":"1.0.0"},"channels":{"mychannel":{"publish":{"traits":[{"externalDocs":{"url":"https://company.com/docs"}}],"externalDocs":{"x-extension":true,"url":"https://irrelevant.com"},"message":{"traits":[{"x-some-extension":"some extension"}],"payload":{"type":"object","properties":{"name":{"type":"string"},"test":null}}}}},"oneOfMessageChannel":{"publish":{"message":{"oneOf":[{"traits":[{"x-some-extension":"some extension"}],"payload":{"type":"object","properties":{"name":{"type":"string"},"test":null}}}]}}}},"components":{"messages":{"testMessage":{"traits":[{"x-some-extension":"some extension"}],"payload":{"type":"object","properties":{"name":{"type":"string"},"test":null}}}},"schemas":{"testSchema":{"type":"object","properties":{"name":{"type":"string"},"test":null}}},"messageTraits":{"extension":{"x-some-extension":"some extension"}},"operationTraits":{"docs":{"externalDocs":{"url":"https://company.com/docs"}}}}}'; | ||
const invalidAsyncAPI = '{"asyncapi":"2.0.0","info":{}}'; | ||
const eolLength = EOL.length; | ||
const checkErrorTypeAndMessage = async (fn, type, message) => { | ||
/* eslint-disable sonarjs/cognitive-complexity */ | ||
/** | ||
* Disabled the rule for this function as there is no way to make it shorter in a meaningfull way | ||
* This function should always be used in tests where errors are evaluated to make sure they always work even if proper error is not thrown | ||
* @private | ||
* @param {Function} fn Function that you want to test | ||
* @param {Object} validationObject Error object to evaluate against the error thrown by fn() | ||
*/ | ||
const checkErrorWrapper = async (fn, validationObject) => { | ||
const { type, message, title, refs, detail, location, validationErrors, parsedJSON } = validationObject; | ||
try { | ||
await fn(); | ||
throw Error('should not be reachable'); | ||
throw Error('This error should not be reachable. If you reached it, it means the function did not throw a proper error and executed successfully.'); | ||
} catch (e) { | ||
expect(e instanceof ParserError).to.equal(true); | ||
expect(e).to.have.own.property('type', type); | ||
expect(e).to.have.own.property('message', message); | ||
} | ||
}; | ||
const isProperError = e instanceof ParserError; | ||
if (!isProperError) console.log(e); | ||
const checkErrorParsedJSON = async (fn, parsedJSON) => { | ||
try { | ||
await fn(); | ||
throw Error('should not be reachable'); | ||
} catch (e) { | ||
expect(JSON.stringify(e.parsedJSON)).to.equal(parsedJSON); | ||
if (isProperError) expect(e instanceof ParserError).to.equal(true); | ||
if (type) expect(e).to.have.own.property('type', type); | ||
if (message) expect(e).to.have.own.property('message', message); | ||
if (title) expect(e).to.have.own.property('title', title); | ||
if (detail) expect(e).to.have.own.property('detail', detail); | ||
if (refs) expect(e.refs).to.deep.equal(refs); | ||
if (location) expect(e.location).to.deep.equal(location); | ||
if (validationErrors) expect(e.validationErrors).to.deep.equal(validationErrors); | ||
if (parsedJSON) expect(JSON.stringify(e.parsedJSON)).to.deep.equal(parsedJSON); | ||
} | ||
}; | ||
const offset = (offset, line) => (offset + ((eolLength - 1) * (line - 1))); | ||
const offset = (oset, line) => (oset + ((eolLength - 1) * (line - 1))); | ||
@@ -56,14 +66,27 @@ describe('parse()', function() { | ||
it('should fail when asyncapi is not valid', async function() { | ||
try { | ||
await parser.parse(invalidAsyncAPI); | ||
} catch (e) { | ||
await expect(e.type).to.equal('https://github.com/asyncapi/parser-js/validation-errors'); | ||
await expect(e.title).to.equal('There were errors validating the AsyncAPI document.'); | ||
await expect(e.validationErrors).to.deep.equal([{ | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/validation-errors', | ||
title: 'There were errors validating the AsyncAPI document.', | ||
parsedJSON: invalidAsyncAPI, | ||
validationErrors: [{ | ||
title: '/info should have required property \'title\'', | ||
location: { jsonPointer: '/info' } | ||
location: { | ||
endColumn: 31, | ||
endLine: 1, | ||
endOffset: 29, | ||
jsonPointer: '/info', | ||
startColumn: 29, | ||
startLine: 1, | ||
startOffset: 27 | ||
} | ||
}, | ||
{ | ||
title: '/info should have required property \'version\'', | ||
location: { jsonPointer: '/info' } | ||
location: { endColumn: 31, | ||
endLine: 1, | ||
endOffset: 29, | ||
jsonPointer: '/info', | ||
startColumn: 29, | ||
startLine: 1, | ||
startOffset: 27 } | ||
}, | ||
@@ -73,177 +96,250 @@ { | ||
location: { jsonPointer: '/' } | ||
}]); | ||
await expect(e.parsedJSON).to.deep.equal(invalidAsyncAPI); | ||
} | ||
}] | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(invalidAsyncAPI); | ||
}, expectedErrorObject); | ||
}); | ||
it('should fail when asyncapi is not valid (yaml)', async function() { | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/validation-errors', | ||
title: 'There were errors validating the AsyncAPI document.', | ||
parsedJSON: invalidYamlOutput, | ||
validationErrors: [ | ||
{ | ||
title: '/info should have required property \'title\'', | ||
location: { | ||
jsonPointer: '/info', | ||
startLine: 2, | ||
startColumn: 1, | ||
startOffset: offset(16, 2), | ||
endLine: 3, | ||
endColumn: 19, | ||
endOffset: offset(40, 3), | ||
} | ||
} | ||
] | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(invalidAsyncapiYAML, { path: __filename }); | ||
} catch (e) { | ||
await expect(e.type).to.equal('https://github.com/asyncapi/parser-js/validation-errors'); | ||
await expect(e.title).to.equal('There were errors validating the AsyncAPI document.'); | ||
await expect(e.validationErrors).to.deep.equal([{ | ||
title: '/info should have required property \'title\'', | ||
location: { | ||
jsonPointer: '/info', | ||
startLine: 2, | ||
startColumn: 1, | ||
startOffset: offset(16, 2), | ||
endLine: 3, | ||
endColumn: 19, | ||
endOffset: offset(40, 3), | ||
} | ||
}]); | ||
await expect(JSON.stringify(e.parsedJSON)).to.equal(invalidYamlOutput); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
it('should fail when asyncapi is not valid (ref with line break) (yaml)', async function() { | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/validation-errors', | ||
title: 'There were errors validating the AsyncAPI document.', | ||
validationErrors: [ | ||
{ | ||
title: '/channels/smartylighting~1streetlights~11~10~1action~1{streetlightId}~1turn~1off/parameters/streetlightId/$ref should match format \"uri-reference\"', | ||
location: { | ||
jsonPointer: '/channels/smartylighting~1streetlights~11~10~1action~1{streetlightId}~1turn~1off/parameters/streetlightId/$ref', | ||
startLine: 67, | ||
startColumn: 9, | ||
startOffset: offset(1970, 67), | ||
endLine: 68, | ||
endColumn: 46, | ||
endOffset: offset(2024, 68), | ||
} | ||
} | ||
] | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(fs.readFileSync(path.resolve(__dirname, './wrong/invalid-asyncapi-with-ref-with-line-break.yaml'), 'utf8'), { | ||
path: __filename, | ||
}); | ||
} catch (e) { | ||
await expect(e.type).to.equal('https://github.com/asyncapi/parser-js/validation-errors'); | ||
await expect(e.title).to.equal('There were errors validating the AsyncAPI document.'); | ||
await expect(e.validationErrors).to.deep.equal([{ | ||
title: '/channels/smartylighting~1streetlights~11~10~1action~1{streetlightId}~1turn~1off/parameters/streetlightId/$ref should match format \"uri-reference\"', | ||
location: { | ||
jsonPointer: '/channels/smartylighting~1streetlights~11~10~1action~1{streetlightId}~1turn~1off/parameters/streetlightId/$ref', | ||
startLine: 67, | ||
startColumn: 9, | ||
startOffset: offset(1970, 67), | ||
endLine: 68, | ||
endColumn: 46, | ||
endOffset: offset(2024, 68), | ||
} | ||
}]); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
it('should fail when asyncapi is not valid (json)', async function() { | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/validation-errors', | ||
title: 'There were errors validating the AsyncAPI document.', | ||
parsedJSON: invalidJsonOutput, | ||
validationErrors: [ | ||
{ | ||
title: '/info should have required property \'title\'', | ||
location: { | ||
jsonPointer: '/info', | ||
startLine: 3, | ||
startColumn: 11, | ||
startOffset: offset(33, 3), | ||
endLine: 5, | ||
endColumn: 4, | ||
endOffset: offset(58, 5), | ||
} | ||
} | ||
] | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(invalidAsyncpiJSON, { path: __filename }); | ||
} catch (e) { | ||
await expect(e.type).to.equal('https://github.com/asyncapi/parser-js/validation-errors'); | ||
await expect(e.title).to.equal('There were errors validating the AsyncAPI document.'); | ||
await expect(e.validationErrors).to.deep.equal([{ | ||
title: '/info should have required property \'title\'', | ||
location: { | ||
jsonPointer: '/info', | ||
startLine: 3, | ||
startColumn: 11, | ||
startOffset: offset(33, 3), | ||
endLine: 5, | ||
endColumn: 4, | ||
endOffset: offset(58, 5), | ||
} | ||
}]); | ||
await expect(JSON.stringify(e.parsedJSON)).to.equal(invalidJsonOutput); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
it('should fail when it is not possible to convert asyncapi to json', async function() { | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/impossible-to-convert-to-json', | ||
title: 'Could not convert AsyncAPI to JSON.', | ||
detail: 'Most probably the AsyncAPI document contains invalid YAML or YAML features not supported in JSON.' | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse('bad'); | ||
} catch (e) { | ||
await expect(e.type).to.equal('https://github.com/asyncapi/parser-js/impossible-to-convert-to-json'); | ||
await expect(e.title).to.equal('Could not convert AsyncAPI to JSON.'); | ||
await expect(e.detail).to.equal('Most probably the AsyncAPI document contains invalid YAML or YAML features not supported in JSON.'); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
it('should fail when asyncapi is not present', async function() { | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/missing-asyncapi-field', | ||
title: 'The `asyncapi` field is missing.', | ||
parsedJSON: '{"bad":true}' | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse('bad: true'); | ||
} catch (e) { | ||
await expect(e.type).to.equal('https://github.com/asyncapi/parser-js/missing-asyncapi-field'); | ||
await expect(e.title).to.equal('The `asyncapi` field is missing.'); | ||
await expect(e.parsedJSON).to.deep.equal({ bad: true }); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
it('should fail when asyncapi version is not supported', async function() { | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/unsupported-version', | ||
title: 'Version 1.2.0 is not supported.', | ||
detail: 'Please use latest version of the specification.', | ||
parsedJSON: '{"asyncapi":"1.2.0"}', | ||
validationErrors: [ | ||
{ | ||
jsonPointer: '/asyncapi', | ||
startLine: 1, | ||
startColumn: 1, | ||
startOffset: 0, | ||
endLine: 1, | ||
endColumn: 16, | ||
endOffset: offset(15, 1), | ||
} | ||
] | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse('asyncapi: 1.2.0'); | ||
} catch (e) { | ||
await expect(e.type).to.equal('https://github.com/asyncapi/parser-js/unsupported-version'); | ||
await expect(e.title).to.equal('Version 1.2.0 is not supported.'); | ||
await expect(e.detail).to.equal('Please use latest version of the specification.'); | ||
await expect(e.parsedJSON).to.deep.equal({ asyncapi: '1.2.0' }); | ||
await expect(e.validationErrors).to.deep.equal([{ | ||
jsonPointer: '/asyncapi', | ||
startLine: 1, | ||
startColumn: 1, | ||
startOffset: 0, | ||
endLine: 1, | ||
endColumn: 16, | ||
endOffset: offset(15, 1), | ||
}]); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
it('should fail when asyncapi is not yaml nor json', async function() { | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/invalid-yaml', | ||
title: 'The provided YAML is not valid.', | ||
detail: 'duplicated mapping key at line 2, column -4:\n bad:\n ^', | ||
location: { startOffset: 5, startLine: 2, startColumn: -4 } | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse('bad:\nbad:'); | ||
} catch (e) { | ||
await expect(e.type).to.equal('https://github.com/asyncapi/parser-js/invalid-yaml'); | ||
await expect(e.title).to.equal('The provided YAML is not valid.'); | ||
await expect(e.detail).to.equal('duplicated mapping key at line 2, column -4:\n bad:\n ^'); | ||
await expect(e.location).to.deep.equal({ startOffset: 5, startLine: 2, startColumn: -4 }); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
it('should fail to resolve relative files when options.path is not provided', async function() { | ||
const type = 'https://github.com/asyncapi/parser-js/dereference-error'; | ||
const message = `Error opening file "${path.resolve(process.cwd(), 'refs/refed.yaml')}" \nENOENT: no such file or directory, open '${path.resolve(process.cwd(), 'refs/refed.yaml')}'`; | ||
const testFn = async () => { await parser.parse(inputYAML); }; | ||
await checkErrorTypeAndMessage(testFn, type, message); | ||
await checkErrorParsedJSON(testFn, outputJsonWithRefs); | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/dereference-error', | ||
title: `Error opening file "${path.resolve(process.cwd(), 'refs/refed.yaml')}" \nENOENT: no such file or directory, open '${path.resolve(process.cwd(), 'refs/refed.yaml')}'`, | ||
parsedJSON: outputJsonWithRefs, | ||
refs: [ | ||
{ | ||
jsonPointer: '/components/schemas/testSchema/properties/test/$ref', | ||
startLine: 35, | ||
startColumn: 11, | ||
startOffset: 736, | ||
endLine: 35, | ||
endColumn: 34, | ||
endOffset: 759 | ||
} | ||
] | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(inputYAML); | ||
}, expectedErrorObject); | ||
}); | ||
it('should offer information about YAML line and column where $ref errors are located', async function() { | ||
try { | ||
await parser.parse(inputYAML, { path: __filename }); | ||
} catch (e) { | ||
expect(e.refs).to.deep.equal([{ | ||
jsonPointer: '/components/schemas/testSchema/properties/test/$ref', | ||
startLine: 30, | ||
startColumn: 11, | ||
startOffset: offset(615, 30), | ||
endLine: 30, | ||
endColumn: 34, | ||
endOffset: offset(638, 30), | ||
}]); | ||
} | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/dereference-error', | ||
title: `Error opening file "${path.resolve(process.cwd(), 'refs/refed.yaml')}" \nENOENT: no such file or directory, open '${path.resolve(process.cwd(), 'refs/refed.yaml')}'`, | ||
refs: [ | ||
{ | ||
jsonPointer: '/components/schemas/testSchema/properties/test/$ref', | ||
startLine: 35, | ||
startColumn: 11, | ||
startOffset: 736, | ||
endLine: 35, | ||
endColumn: 34, | ||
endOffset: 759 | ||
} | ||
] | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(inputYAML); | ||
}, expectedErrorObject); | ||
}); | ||
it('should offer information about JSON line and column where $ref errors are located', async function() { | ||
try { | ||
await parser.parse(inputJSON, { path: __filename }); | ||
} catch (e) { | ||
expect(e.refs).to.deep.equal([{ | ||
jsonPointer: '/components/schemas/testSchema/properties/test/$ref', | ||
startLine: 38, | ||
startColumn: 21, | ||
startOffset: offset(599, 38), | ||
endLine: 38, | ||
endColumn: 38, | ||
endOffset: offset(616, 38), | ||
}]); | ||
} | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/dereference-error', | ||
title: `Error opening file "${path.resolve(process.cwd(), 'refs/refed.yaml')}" \nENOENT: no such file or directory, open '${path.resolve(process.cwd(), 'refs/refed.yaml')}'`, | ||
refs: [ | ||
{ | ||
jsonPointer: '/components/schemas/testSchema/properties/test/$ref', | ||
startLine: 38, | ||
startColumn: 21, | ||
startOffset: offset(599, 38), | ||
endLine: 38, | ||
endColumn: 38, | ||
endOffset: offset(616, 38) | ||
} | ||
] | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(inputJSON); | ||
}, expectedErrorObject); | ||
}); | ||
it('should not offer information about JS line and column where $ref errors are located if format is JS', async function() { | ||
try { | ||
await parser.parse(JSON.parse(inputJSON), { path: __filename }); | ||
} catch (e) { | ||
expect(e.refs).to.deep.equal([{ | ||
jsonPointer: '/components/schemas/testSchema/properties/test/$ref', | ||
}]); | ||
} | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/dereference-error', | ||
title: `Error opening file "${path.resolve(process.cwd(), 'refs/refed.yaml')}" \nENOENT: no such file or directory, open '${path.resolve(process.cwd(), 'refs/refed.yaml')}'`, | ||
refs: [ | ||
{ | ||
jsonPointer: '/components/schemas/testSchema/properties/test/$ref', | ||
} | ||
] | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(JSON.parse(inputJSON)); | ||
}, expectedErrorObject); | ||
}); | ||
it('should offer information about missing HTTP $refs', async function() { | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/dereference-error', | ||
title: 'Error downloading https://example.com/components/messages/testMessage \nHTTP ERROR 404', | ||
refs: [ | ||
{ | ||
jsonPointer: '/channels/mychannel/publish/message/$ref', | ||
startLine: 9, | ||
startColumn: 9, | ||
startOffset: offset(116, 9), | ||
endLine: 9, | ||
endColumn: 68, | ||
endOffset: offset(175, 9), | ||
} | ||
] | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(fs.readFileSync(path.resolve(__dirname, './wrong/inexisting-http-ref.yaml'), 'utf8'), { | ||
@@ -255,17 +351,23 @@ path: 'https://example.com', | ||
}); | ||
} catch (e) { | ||
expect(e.refs).to.deep.equal([{ | ||
jsonPointer: '/channels/mychannel/publish/message/$ref', | ||
startLine: 9, | ||
startColumn: 9, | ||
startOffset: offset(116, 9), | ||
endLine: 9, | ||
endColumn: 68, | ||
endOffset: offset(175, 9), | ||
}]); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
it('should offer information about missing root $refs', async function() { | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/dereference-error', | ||
title: 'Error downloading https://example.com/components/messages/testMessage \nHTTP ERROR 404', | ||
refs: [ | ||
{ | ||
jsonPointer: '/channels/mychannel/subscribe/message/$ref', | ||
startLine: 9, | ||
startColumn: 9, | ||
startOffset: offset(118, 9), | ||
endLine: 9, | ||
endColumn: 49, | ||
endOffset: offset(158, 9), | ||
} | ||
] | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(fs.readFileSync(path.resolve(__dirname, './wrong/inexisting-root-ref.yaml'), 'utf8'), { | ||
@@ -277,17 +379,23 @@ path: 'https://example.com', | ||
}); | ||
} catch (e) { | ||
expect(e.refs).to.deep.equal([{ | ||
jsonPointer: '/channels/mychannel/subscribe/message/$ref', | ||
startLine: 9, | ||
startColumn: 9, | ||
startOffset: offset(118, 9), | ||
endLine: 9, | ||
endColumn: 49, | ||
endOffset: offset(158, 9), | ||
}]); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
it('should offer information about missing local $refs', async function() { | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/dereference-error', | ||
title: 'Token "components" does not exist.', | ||
refs: [ | ||
{ | ||
jsonPointer: '/channels/mychannel2/publish/message/$ref', | ||
startLine: 9, | ||
startColumn: 9, | ||
startOffset: offset(117, 9), | ||
endLine: 9, | ||
endColumn: 50, | ||
endOffset: offset(158, 9), | ||
} | ||
] | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(fs.readFileSync(path.resolve(__dirname, './wrong/inexisting-local-ref.yaml'), 'utf8'), { | ||
@@ -299,88 +407,86 @@ path: 'https://example.com', | ||
}); | ||
} catch (e) { | ||
expect(e.refs).to.deep.equal([{ | ||
jsonPointer: '/channels/mychannel2/publish/message/$ref', | ||
startLine: 9, | ||
startColumn: 9, | ||
startOffset: offset(117, 9), | ||
endLine: 9, | ||
endColumn: 50, | ||
endOffset: offset(158, 9), | ||
}]); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
it('should throw error if document is invalid YAML', async function() { | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/invalid-yaml', | ||
title: 'The provided YAML is not valid.', | ||
detail: 'bad indentation of a mapping entry at line 19, column 11:\n $ref: "#/components/schemas/sentAt"\n ^', | ||
location: { startOffset: 460, startLine: 19, startColumn: 11 } | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(invalidYAML, { path: __filename }); | ||
} catch (e) { | ||
expect(e.type).to.equal('https://github.com/asyncapi/parser-js/invalid-yaml'); | ||
expect(e.title).to.equal('The provided YAML is not valid.'); | ||
expect(e.detail).to.equal('bad indentation of a mapping entry at line 19, column 11:\n $ref: "#/components/schemas/sentAt"\n ^'); | ||
expect(e.location).to.deep.equal({ startOffset: offset(460, 19), startLine: 19, startColumn: 11 }); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
it('should throw error if document is invalid JSON', async function() { | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/invalid-json', | ||
title: 'The provided JSON is not valid.', | ||
detail: 'Unexpected token j in JSON at position 12 while parsing near \' {"invalid "json" }\'', | ||
location: { startOffset: 12, startLine: 1, startColumn: 12 } | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(' {"invalid "json" }'); | ||
} catch (e) { | ||
expect(e.type).to.equal('https://github.com/asyncapi/parser-js/invalid-json'); | ||
expect(e.title).to.equal('The provided JSON is not valid.'); | ||
expect(e.detail).to.equal('Unexpected token j in JSON at position 12 while parsing near \' {"invalid "json" }\''); | ||
expect(e.location).to.deep.equal({ startOffset: 12, startLine: 1, startColumn: 12 }); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
it('should throw error if document is null or falsey', async function() { | ||
const type = 'https://github.com/asyncapi/parser-js/null-or-falsey-document'; | ||
const message = 'Document can\'t be null or falsey.'; | ||
await checkErrorTypeAndMessage(async () => { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/null-or-falsey-document', | ||
title: 'Document can\'t be null or falsey.', | ||
}; | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(''); | ||
}, type, message); | ||
await checkErrorTypeAndMessage(async () => { | ||
}, expectedErrorObject); | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(false); | ||
}, type, message); | ||
await checkErrorTypeAndMessage(async () => { | ||
}, expectedErrorObject); | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(null); | ||
}, type, message); | ||
await checkErrorTypeAndMessage(async () => { | ||
}, expectedErrorObject); | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(undefined); | ||
}, type, message); | ||
await checkErrorTypeAndMessage(async () => { | ||
}, expectedErrorObject); | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(NaN); | ||
}, type, message); | ||
}, expectedErrorObject); | ||
}); | ||
it('should throw error if document is not string nor object', async function() { | ||
const type = 'https://github.com/asyncapi/parser-js/invalid-document-type'; | ||
const message = 'The AsyncAPI document has to be either a string or a JS object.'; | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/invalid-document-type', | ||
title: 'The AsyncAPI document has to be either a string or a JS object.', | ||
}; | ||
await checkErrorTypeAndMessage(async () => { | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(true); | ||
}, type, message); | ||
await checkErrorTypeAndMessage(async () => { | ||
}, expectedErrorObject); | ||
await checkErrorWrapper(async () => { | ||
await parser.parse([]); | ||
}, type, message); | ||
await checkErrorTypeAndMessage(async () => { | ||
}, expectedErrorObject); | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(new Map()); | ||
}, type, message); | ||
await checkErrorTypeAndMessage(async () => { | ||
}, expectedErrorObject); | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(new Set()); | ||
}, type, message); | ||
await checkErrorTypeAndMessage(async () => { | ||
}, expectedErrorObject); | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(new WeakMap()); | ||
}, type, message); | ||
await checkErrorTypeAndMessage(async () => { | ||
}, expectedErrorObject); | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(new WeakSet()); | ||
}, type, message); | ||
await checkErrorTypeAndMessage(async () => { | ||
}, expectedErrorObject); | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(1); | ||
}, type, message); | ||
await checkErrorTypeAndMessage(async () => { | ||
}, expectedErrorObject); | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(Symbol('test')); | ||
}, type, message); | ||
await checkErrorTypeAndMessage(async () => { | ||
}, expectedErrorObject); | ||
await checkErrorWrapper(async () => { | ||
await parser.parse(() => {}); | ||
}, type, message); | ||
}, expectedErrorObject); | ||
}); | ||
@@ -425,3 +531,3 @@ | ||
it('should throw error that required functions are missing', function() { | ||
it('should throw error that required functions are missing', async function() { | ||
const parserModule = { | ||
@@ -431,9 +537,11 @@ parse: () => {} | ||
try { | ||
const expectedErrorObject = { | ||
type: 'https://github.com/asyncapi/parser-js/impossible-to-register-parser', | ||
title: 'parserModule must have parse() and getMimeTypes() functions.' | ||
}; | ||
await checkErrorWrapper(async () => { | ||
parser.registerSchemaParser(parserModule); | ||
} catch (e) { | ||
expect(e.type).to.equal('https://github.com/asyncapi/parser-js/impossible-to-register-parser'); | ||
expect(e.title).to.equal('parserModule must have parse() and getMimeTypes() functions.'); | ||
} | ||
}, expectedErrorObject); | ||
}); | ||
}); |
@@ -453,7 +453,14 @@ declare module "@asyncapi/parser" { | ||
* Registers a new schema parser. Schema parsers are in charge of parsing and transforming payloads to AsyncAPI Schema format. | ||
* @param schemaFormats - An array of schema formats the given schema parser is able to recognize and transform. | ||
* @param parserModule - The schema parser module containing parse() and getMimeTypes() functions. | ||
*/ | ||
function registerSchemaParser(schemaFormats: string[], parserModule: any): void; | ||
function registerSchemaParser(parserModule: any): void; | ||
/** | ||
* Triggers additional operations on the AsyncAPI channels like traits application or message validation and conversion | ||
* @param parsedJSON - parsed AsyncAPI document | ||
* @param asyncapiYAMLorJSON - AsyncAPI document in string | ||
* @param initialFormat - information of the document was oryginally JSON or YAML | ||
* @param options - Configuration options. | ||
*/ | ||
function customChannelsOperations(parsedJSON: any, asyncapiYAMLorJSON: string, initialFormat: string, options: any): void; | ||
} | ||
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
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
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
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
GitHub dependency
Supply chain riskContains a dependency which resolves to a GitHub URL. Dependencies fetched from GitHub specifiers are not immutable can be used to inject untrusted code or reduce the likelihood of a reproducible install.
Found 1 instance in 1 package
1115880
9569
0
13
+ Added@apidevtools/json-schema-ref-parser@9.1.2(transitive)
+ Added@jsdevtools/ono@7.1.3(transitive)
+ Added@types/json-schema@7.0.15(transitive)
+ Addedargparse@2.0.1(transitive)
+ Addedcall-me-maybe@1.0.2(transitive)
+ Addedjs-yaml@4.1.0(transitive)