diff --git a/lib/bundle.js b/lib/bundle.js index c0b7b83..0c9d130 100644 --- a/lib/bundle.js +++ b/lib/bundle.js @@ -46,6 +46,16 @@ function parseFileOrThrow(fileContent) { return result; } +/** + * Parses a node content or throw ParseError if there's any error + * @param {string} fileContent The content from the current node + * @returns {object} The parsed content + */ +function parseFile(fileContent) { + const result = parse.getOasObject(fileContent); + return result; +} + /** * Calculates the path relative to parent * @param {string} parentFileName - parent file name of the current node @@ -269,13 +279,15 @@ function handleLocalCollisions(trace, initialMainKeys) { * @param {object} version - The version of the spec we are bundling * @param {object} rootMainKeys - A dictionary with the component keys in local components object and its mainKeys * @param {string} commonPathFromData - The common path in the file's paths + * @param {Array} allData - array of { path, content} objects * @returns {object} - The references in current node and the new content from the node */ function getReferences (currentNode, isOutOfRoot, pathSolver, parentFilename, version, rootMainKeys, - commonPathFromData) { + commonPathFromData, allData) { let referencesInNode = [], nodeReferenceDirectory = {}, mainKeys = {}; + traverseUtility(currentNode).forEach(function (property) { if (property) { let hasReferenceTypeKey; @@ -306,10 +318,21 @@ function getReferences (currentNode, isOutOfRoot, pathSolver, parentFilename, ve return item !== undefined; }), this.key]; let newValue, - [, local] = tempRef.split(localPointer); + [, local] = tempRef.split(localPointer), + nodeFromData, + refHasContent = false, + parseResult; newValue = Object.assign({}, this.node); - newValue.$ref = referenceInDocument; + nodeFromData = findNodeFromPath(tempRef, allData); + if (nodeFromData && nodeFromData.content) { + parseResult = parseFile(nodeFromData.content); + if (parseResult.result) { + newValue.$ref = referenceInDocument; + refHasContent = true; + nodeFromData.parsed = parseResult; + } + } this.update({ $ref: tempRef }); @@ -320,7 +343,8 @@ function getReferences (currentNode, isOutOfRoot, pathSolver, parentFilename, ve reference: referenceInDocument, traceToParent, parentNodeKey: parentFilename, - mainKeyInTrace: nodeTrace[nodeTrace.length - 1] + mainKeyInTrace: nodeTrace[nodeTrace.length - 1], + refHasContent }; mainKeys[componentKey] = tempRef; @@ -348,13 +372,18 @@ function getReferences (currentNode, isOutOfRoot, pathSolver, parentFilename, ve function getNodeContentAndReferences (currentNode, allData, specRoot, version, rootMainKeys, commonPathFromData) { let graphAdj = [], missingNodes = [], - nodeContent; + nodeContent, + parseResult; if (currentNode.parsed) { nodeContent = currentNode.parsed.oasObject; } else { - nodeContent = parseFileOrThrow(currentNode.content).oasObject; + parseResult = parseFile(currentNode.content); + if (parseResult.result === false) { + return { graphAdj, missingNodes, undefined, nodeReferenceDirectory: {}, nodeName: currentNode.fileName }; + } + nodeContent = parseResult.oasObject; } const { referencesInNode, nodeReferenceDirectory } = getReferences( @@ -364,7 +393,8 @@ function getNodeContentAndReferences (currentNode, allData, specRoot, version, r currentNode.fileName, version, rootMainKeys, - commonPathFromData + commonPathFromData, + allData ); referencesInNode.forEach((reference) => { @@ -404,12 +434,14 @@ function generateComponentsObject (documentContext, rootContent, refTypeResolver const { COMPONENTS_KEYS } = getBundleRulesDataByVersion(version); notInLine.forEach(([key, value]) => { let [, partial] = key.split(localPointer); - setValueInComponents( - value.keyInComponents, - components, - getContentFromTrace(documentContext.nodeContents[key], partial), - COMPONENTS_KEYS - ); + if (documentContext.globalReferences[key].refHasContent) { + setValueInComponents( + value.keyInComponents, + components, + getContentFromTrace(documentContext.nodeContents[key], partial), + COMPONENTS_KEYS + ); + } }); [rootContent, components].forEach((contentData) => { traverseUtility(contentData).forEach(function (property) { @@ -429,12 +461,12 @@ function generateComponentsObject (documentContext, rootContent, refTypeResolver return missingNode.path === nodeRef; }); if (isMissingNode) { - refData.nodeContent = { - [tempRef]: 'This related node was not found in provided data' - }; - refData.inline = true; + refData.nodeContent = refData.node; refData.local = false; } + else if (!refData) { + return; + } else { refData.nodeContent = documentContext.nodeContents[nodeRef]; refData.inline = refData.keyInComponents.length === 0; @@ -453,12 +485,14 @@ function generateComponentsObject (documentContext, rootContent, refTypeResolver } this.update(refData.node); if (!refData.inline) { - setValueInComponents( - refData.keyInComponents, - components, - refData.nodeContent, - COMPONENTS_KEYS - ); + if (documentContext.globalReferences[tempRef].refHasContent) { + setValueInComponents( + refData.keyInComponents, + components, + refData.nodeContent, + version + ); + } } } } diff --git a/lib/parse.js b/lib/parse.js index 80ff638..5923d34 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -96,9 +96,11 @@ module.exports = { * @param {Object} options computed process options * @param {Object} files Files map * @param {string} specificationVersion the string of the desired version + * @param {boolean} allowReadingFS wheter to allow reading content from file system * @return {String} rootFile */ - getRootFiles: function (input, inputValidation, options, files = {}, specificationVersion) { + getRootFiles: function (input, inputValidation, options, files = {}, specificationVersion, + allowReadingFS = true) { let rootFilesArray = [], filesPathArray = input.data, origin = input.origin || ''; @@ -114,7 +116,7 @@ module.exports = { if (!_.isEmpty(files)) { file = files[path.resolve(filePath.fileName)]; } - else { + else if (allowReadingFS) { file = fs.readFileSync(filePath.fileName, 'utf8'); } diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index 87f0565..ae75338 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -4854,7 +4854,7 @@ module.exports = { bundledFile[key] = value; }); } - else { + else if (!_.isEmpty(contentAndComponents.components)) { bundledFile.components = contentAndComponents.components; } if (!format) { diff --git a/lib/schemapack.js b/lib/schemapack.js index a2212e5..6b72436 100644 --- a/lib/schemapack.js +++ b/lib/schemapack.js @@ -643,15 +643,13 @@ class SchemaPack { path = pathBrowserify; OasResolverOptions.browser = true; } - if ('content' in input.data[0]) { - input.data.forEach((file) => { - files[path.resolve(file.fileName)] = file.content ? file.content : ''; - }); - } + input.data.forEach((file) => { + files[path.resolve(file.fileName)] = file.content ? file.content : ''; + }); adaptedInput = schemaUtils.mapDetectRootFilesInputToGetRootFilesInput(input); adaptedInput.origin = input.origin; rootFiles = parse.getRootFiles(adaptedInput, concreteUtils.inputValidation, this.computedOptions, files, - input.specificationVersion); + input.specificationVersion, false); res = schemaUtils.mapGetRootFilesOutputToDetectRootFilesOutput(rootFiles, input.specificationVersion); return res; } diff --git a/test/data/toBundleExamples/local_references/expectedNodeNotProvided.json b/test/data/toBundleExamples/local_references/expectedNodeNotProvided.json index 9a8dd9c..ed726ee 100644 --- a/test/data/toBundleExamples/local_references/expectedNodeNotProvided.json +++ b/test/data/toBundleExamples/local_references/expectedNodeNotProvided.json @@ -60,7 +60,7 @@ }, "components": { "responses": { - "/responses.yaml": "This related node was not found in provided data" + "$ref": "./responses.yaml" }, "schemas": { "Monster": { @@ -98,4 +98,4 @@ } } } -} \ No newline at end of file +} diff --git a/test/unit/bundle.test.js b/test/unit/bundle.test.js index bbd738c..f160d48 100644 --- a/test/unit/bundle.test.js +++ b/test/unit/bundle.test.js @@ -657,8 +657,8 @@ describe('bundle files method - 3.0', function () { expect(JSON.stringify(JSON.parse(res.output.data[0].bundledContent), null, 2)).to.be.equal(expected); }); - it('Should return a "/missing/node/path": NotProvided' + - ' in the place of a not providen node - local_references', async function () { + it('Should return the not handled reference ($ref: ./responses.yaml) ' + + 'in the place of a not provided node - local_references', async function () { let contentRootFile = fs.readFileSync(localRefFolder + '/root.yaml', 'utf8'), schemasIndex = fs.readFileSync(localRefFolder + '/schemas/index.yaml', 'utf8'), schemasClient = fs.readFileSync(localRefFolder + '/schemas/client.yaml', 'utf8'), @@ -2060,6 +2060,156 @@ describe('bundle files method - 3.0', function () { expect(res.output.specification.version).to.equal('3.0'); expect(JSON.stringify(JSON.parse(res.output.data[0].bundledContent), null, 2)).to.be.equal(expected); }); + it('should ignore reference when is empty content and no root is sent', async function () { + let input = + { + type: 'multiFile', + specificationVersion: '3.0', + bundleFormat: 'YAML', + data: [ + { + path: 'hello.yaml', + content: '' + }, + { + path: 'openapi.yaml', + content: 'openapi: 3.0.0\n' + + 'info:\n' + + ' title: hello world\n' + + ' version: 0.1.1\n' + + 'paths:\n' + + ' /hello:\n' + + ' get:\n' + + ' summary: get the hello\n' + + ' responses:\n' + + ' \'200\':\n' + + ' description: sample des\n' + + ' content:\n' + + ' application/json:\n' + + ' schema:\n' + + ' $ref: ./hello.yaml\n' + } + ] + }; + const res = await Converter.bundle(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + expect(res.output.specification.version).to.equal('3.0'); + expect(res.output.data[0].bundledContent).to.be.equal(input.data[1].content); + }); + + it('should ignore reference when is empty content', async function () { + let input = + { + type: 'multiFile', + specificationVersion: '3.0', + bundleFormat: 'YAML', + rootFiles: [{ path: 'openapi.yaml' }], + data: [ + { + path: 'hello.yaml', + content: '' + }, + { + path: 'openapi.yaml', + content: 'openapi: 3.0.0\n' + + 'info:\n' + + ' title: hello world\n' + + ' version: 0.1.1\n' + + 'paths:\n' + + ' /hello:\n' + + ' get:\n' + + ' summary: get the hello\n' + + ' responses:\n' + + ' \'200\':\n' + + ' description: sample des\n' + + ' content:\n' + + ' application/json:\n' + + ' schema:\n' + + ' $ref: ./hello.yaml\n' + } + ] + }; + const res = await Converter.bundle(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + expect(res.output.specification.version).to.equal('3.0'); + expect(res.output.data[0].bundledContent).to.be.equal(input.data[1].content); + }); + + it('should ignore reference when is invalid content', async function () { + let input = + { + type: 'multiFile', + specificationVersion: '3.0', + bundleFormat: 'YAML', + rootFiles: [{ path: 'openapi.yaml' }], + data: [ + { + path: 'hello.yaml', + content: 'asd' + }, + { + path: 'openapi.yaml', + content: 'openapi: 3.0.0\n' + + 'info:\n' + + ' title: hello world\n' + + ' version: 0.1.1\n' + + 'paths:\n' + + ' /hello:\n' + + ' get:\n' + + ' summary: get the hello\n' + + ' responses:\n' + + ' \'200\':\n' + + ' description: sample des\n' + + ' content:\n' + + ' application/json:\n' + + ' schema:\n' + + ' $ref: ./hello.yaml\n' + } + ] + }; + const res = await Converter.bundle(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + expect(res.output.specification.version).to.equal('3.0'); + expect(res.output.data[0].bundledContent).to.be.equal(input.data[1].content); + }); + + it('should ignore reference when is invalid', async function () { + let input = + { + type: 'multiFile', + specificationVersion: '3.0', + bundleFormat: 'YAML', + rootFiles: [{ path: 'openapi.yaml' }], + data: [ + { + path: 'openapi.yaml', + content: 'openapi: 3.0.0\n' + + 'info:\n' + + ' title: hello world\n' + + ' version: 0.1.1\n' + + 'paths:\n' + + ' /hello:\n' + + ' get:\n' + + ' summary: get the hello\n' + + ' responses:\n' + + ' \'200\':\n' + + ' description: sample des\n' + + ' content:\n' + + ' application/json:\n' + + ' schema:\n' + + ' $ref: ./hello.yaml\n' + } + ] + }; + const res = await Converter.bundle(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + expect(res.output.specification.version).to.equal('3.0'); + expect(res.output.data[0].bundledContent).to.be.equal(input.data[0].content); + }); }); describe('getReferences method when node does not have any reference', function() { @@ -2111,7 +2261,8 @@ describe('getReferences method when node does not have any reference', function( 'the/parent/filename', '3.0', {}, - '' + '', + [] ); expect(result.nodeReferenceDirectory).to.be.an('object'); expect(Object.keys(result.nodeReferenceDirectory).length).to.equal(1);