From 4aa298b3992bbca19f25ea9a9dc96dc0374a8272 Mon Sep 17 00:00:00 2001 From: Erik Mendoza Date: Fri, 20 May 2022 16:50:07 -0500 Subject: [PATCH] Adding bundle files proces --- lib/bundle.js | 276 +++++++++++------- lib/dfs.js | 7 +- lib/jsonPointer.js | 40 +-- lib/schemaUtils.js | 15 +- .../local_ref/otherSchemas/toy.yaml | 6 + .../toBundleExamples/local_ref/responses.yaml | 8 + .../data/toBundleExamples/local_ref/root.yaml | 50 ++++ .../local_ref/schemas/client.yaml | 7 + .../local_ref/schemas/index.yaml | 21 ++ .../swagger-multi-file/schemas/index.yaml | 7 + .../swagger-multi-file/v1.yaml | 3 +- .../swagger-multi-file/v1_with_local_ref.yaml | 50 ++++ .../swagger-multi-file_easy/root.yaml | 23 ++ .../swagger-multi-file_easy/schemas/user.yaml | 6 + test/unit/bundle.test.js | 89 +++++- test/unit/jsonPointer.test.js | 52 ++-- 16 files changed, 472 insertions(+), 188 deletions(-) create mode 100644 test/data/toBundleExamples/local_ref/otherSchemas/toy.yaml create mode 100644 test/data/toBundleExamples/local_ref/responses.yaml create mode 100644 test/data/toBundleExamples/local_ref/root.yaml create mode 100644 test/data/toBundleExamples/local_ref/schemas/client.yaml create mode 100644 test/data/toBundleExamples/local_ref/schemas/index.yaml create mode 100644 test/data/toBundleExamples/swagger-multi-file/v1_with_local_ref.yaml create mode 100644 test/data/toBundleExamples/swagger-multi-file_easy/root.yaml create mode 100644 test/data/toBundleExamples/swagger-multi-file_easy/schemas/user.yaml diff --git a/lib/bundle.js b/lib/bundle.js index 31938ff..c2d2e8d 100644 --- a/lib/bundle.js +++ b/lib/bundle.js @@ -119,13 +119,112 @@ function getRootFileTrace(nodeParents) { } /** - * Generates a trace from the root to the current item - * @param {array} nodeTrace - The trace from the current file to the current element - * @param {*} connector - The trace from the root's document context to the current file context - * @returns {array} The merged trace from the current item to the root's context + * Get partial content from file content + * @param {object} content - The content in related node + * @param {string} partial - The partial part from reference + * @returns {object} The related content to the trace */ -function getTraceFromParent(nodeTrace, connector) { - return connector.concat(nodeTrace); +function getContentFromTrace(content, partial) { + partial = partial[0] === '/' ? partial.substring(1) : partial; + const trace = partial.split('/'); + let currentValue = content; + for (let place of trace) { + currentValue = currentValue[place]; + } + return currentValue; +} + +/** + * Set a value in the global components object following the provided trace + * @param {array} keyInComponents - The trace to the key in components + * @param {object} components - A global components object + * @param {object} value - The value from node matched with data + * @returns {null} It modifies components global context +*/ +function setValueInComponents(keyInComponents, components, value) { + let currentPlace = components, + target = keyInComponents[keyInComponents.length - 2], + referencedPart = keyInComponents[keyInComponents.length - 1], + [, local] = referencedPart.split('#'), + key = keyInComponents.length === 2 && keyInComponents[0] === 'schema' ? + keyInComponents[1] : + null; + if (keyInComponents[0] === 'schema') { + keyInComponents[0] = 'schemas'; + target = key; + } + + for (let place of keyInComponents) { + if (place === target) { + if (local) { + value = getContentFromTrace(value, local); + } + currentPlace[place] = value; + break; + } + else if (currentPlace[place]) { + currentPlace = currentPlace[place]; + } + else { + currentPlace[place] = {}; + currentPlace = currentPlace[place]; + } + } +} + +/** + * Return a trace from the current node's root to the place where we find a $ref + * @param {object} nodeContext - The current node we are processing + * @param {object} property - The current property that contains the $ref + * @param {string} parentFilename - The parent's filename + * @returns {array} The trace to the place where the $ref appears + */ +function getTraceFromParent(nodeContext, property, parentFilename) { + const parents = [...nodeContext.parents].reverse(), + key = nodeContext.key, + nodeParentsKey = [key, ...parents.map((parent) => { + return parent.key; + })], + nodeTrace = getRootFileTrace(nodeParentsKey), + cleanFileName = (filename) => { + const [file, local] = filename.split('#'); + return [calculatePath(parentFilename, file), local]; + }, + [file, local] = cleanFileName(property.$ref), + keyInComponents = getKeyInComponents(nodeTrace, file, local); + return keyInComponents; +} + +/** + * Returns the key trace if this node will be included in components else returns an empty array + * @param {array} nodeTrace - The trace from file to the $ref + * @returns {array} An arrat with the trace to the key in components + */ +function getKeyParent(nodeTrace) { + const componentsKeys = [ + 'schemas', + 'schema', + 'responses', + 'parameters', + 'examples', + 'requestBodies', + 'headers', + 'securitySchemes', + 'links', + 'callbacks' + ], + trace = [...nodeTrace].reverse(); + let traceToKey = []; + + for (let item of trace) { + traceToKey.push(item); + if (componentsKeys.includes(item)) { + break; + } + } + return traceToKey.length === trace.length ? + [] : + traceToKey.reverse(); } /** @@ -135,11 +234,11 @@ function getTraceFromParent(nodeTrace, connector) { * @param {Function} pathSolver - function to resolve the Path * @param {string} parentFilename - The parent's filename * @param {object} globalComponentsContext - The global context from root file + * @param {array} allData The data from files provided in the input * @returns {object} - {path : $ref value} */ -function getReferences (currentNode, refTypeResolver, pathSolver, parentFilename, globalComponentsContext) { +function getReferences (currentNode, refTypeResolver, pathSolver, parentFilename, globalComponentsContext, allData) { let referencesInNode = []; - traverseUtility(currentNode).forEach(function (property) { if (property) { let hasReferenceTypeKey; @@ -150,66 +249,39 @@ function getReferences (currentNode, refTypeResolver, pathSolver, parentFilename } ); if (hasReferenceTypeKey) { - const parents = [...this.parents].reverse(), - key = this.key, - nodeParentsKey = [key, ...parents.map((parent) => { - return parent.key; - })], - nodeTrace = getRootFileTrace(nodeParentsKey), - connectorFromParent = globalComponentsContext[parentFilename] ? - globalComponentsContext[parentFilename].connector : - [], - traceFromParent = getTraceFromParent(nodeTrace, connectorFromParent), - cleanFileName = (filename) => { - const [file, local] = filename.split('#'); - return [calculatePath(parentFilename, file), local]; - }, - [file, local] = cleanFileName(property.$ref), - newValue = Object.assign({}, this.node), - keyInComponents = getKeyInComponents(traceFromParent, file, local, connectorFromParent), + const nodeTrace = getTraceFromParent(this, property, parentFilename), + keyParent = getKeyParent(nodeTrace), referenceInDocument = getJsonPointerRelationToRoot( jsonPointerEncodeAndReplace, - file, property.$ref, - traceFromParent + keyParent ); + let newValue, + nodeData = findNodeFromPath(calculatePath(parentFilename, property.$ref), allData).content, + nodeContent = nodeData ? + parse.getOasObject(nodeData).oasObject : + { $missedReference: `property ${property.$ref} was not provided in data` }; - newValue.$ref = referenceInDocument; - this.update(newValue); - - if (globalComponentsContext[file]) { - globalComponentsContext[file].isFull = - globalComponentsContext[file].isFull && !local; - if (local) { - globalComponentsContext[file].partialCalled.push(local); - } + if (keyParent.length === 0) { + newValue = nodeContent; } else { - globalComponentsContext[file] = { - calledFrom: parentFilename, - connector: keyInComponents, - isFull: !local, - partialsCalled: local ? [local] : [], - referenceInDocument, - content: this.node - }; + newValue = Object.assign({}, this.node); + newValue.$ref = referenceInDocument; } + globalComponentsContext[property.$ref] = { + newValue: newValue, + keyInComponents: keyParent, + nodeContent + }; + if (!added(property.$ref, referencesInNode)) { - referencesInNode.push({ path: pathSolver(property), keyInComponents }); + referencesInNode.push({ path: pathSolver(property), keyInComponents: keyParent, newValue: this.node }); } } } }); - if (globalComponentsContext[parentFilename]) { - globalComponentsContext[parentFilename].content = currentNode.oasObject; - } - else { - globalComponentsContext[parentFilename] = { - isRoot: true, - filename: parentFilename, - content: currentNode.oasObject - }; - } + return referencesInNode; } @@ -223,17 +295,16 @@ function getReferences (currentNode, refTypeResolver, pathSolver, parentFilename */ function getAdjacentAndMissingToBundle (currentNode, allData, specRoot, globalComponentsContext) { let currentNodeReferences, - currentContent = currentNode.content, graphAdj = [], missingNodes = [], bundleDataInAdjacent = [], OASObject; - if (currentContent.parsed) { - OASObject = currentNode.parsed; + if (currentNode.parsed) { + OASObject = currentNode.parsed.oasObject; } else { - OASObject = parse.getOasObject(currentContent); + OASObject = parse.getOasObject(currentNode.content).oasObject; } currentNodeReferences = getReferences( @@ -241,14 +312,15 @@ function getAdjacentAndMissingToBundle (currentNode, allData, specRoot, globalCo isExtRef, removeLocalReferenceFromPath, currentNode.fileName, - globalComponentsContext + globalComponentsContext, + allData ); currentNodeReferences.forEach((reference) => { let referencePath = reference.path, adjacentNode = findNodeFromPath(calculatePath(currentNode.fileName, referencePath), allData); + if (adjacentNode) { - bundleDataInAdjacent.push({ reference, adjacentNode, currentNode }); graphAdj.push(adjacentNode); } else if (!comparePaths(referencePath, specRoot.fileName)) { @@ -267,56 +339,39 @@ function getAdjacentAndMissingToBundle (currentNode, allData, specRoot, globalCo return { graphAdj, missingNodes, bundleDataInAdjacent, currentNode }; } -// function fillExistentComponents(components, componentsObject) { -// Object.keys(components).forEach((key) => { -// componentsObject[key] = components[key]; -// }); -// return componentsObject; -// } - -/** - * Convert the current key data in document context to an item in components object - * @param {array} namesArray - The conector from root related with the current item - * @param {object} target - The components global object where the result will be added - * @param {string} dataKey - The current key in the document context - * @param {object} documentContext - The document context data necesary to generate the component's items - * @returns {object} The object related with the current key in document context - */ -function convert(namesArray, target, dataKey, documentContext) { - let result = target, - nestedObj = result; - for (let [index, name] of namesArray.entries()) { - let nextName = namesArray[index + 1]; - if (documentContext[name]) { - continue; - } - else if (documentContext[nextName]) { - nestedObj[name] = documentContext[nextName].content; - } - else if (!nestedObj[name]) { - nestedObj[name] = {}; - } - nestedObj = nestedObj[name]; - } - - return result; -} - /** * Generates the components object from the documentContext data * @param {object} documentContext The document context from root - * @param {string} rootFilename - The root's filename + * @param {object} rootContent - The root's parsed content + * @param {function} refTypeResolver - The resolver function to test if node has a reference + * @param {object} components - The global components object * @returns {object} The components object related to the file */ -function generateComponentsObject (documentContext, rootFilename) { - let components = {}; - Object.keys(documentContext).forEach((dataKey) => { - if (dataKey === rootFilename) { - return; - } - convert(documentContext[dataKey].connector, components, dataKey, documentContext); +function generateComponentsObject (documentContext, rootContent, refTypeResolver, components) { + [rootContent, components].forEach((contentData) => { + traverseUtility(contentData).forEach(function (property) { + if (property) { + let hasReferenceTypeKey; + hasReferenceTypeKey = Object.keys(property) + .find( + (key) => { + return refTypeResolver(property, key); + } + ); + if (hasReferenceTypeKey) { + let refData = documentContext[property.$ref]; + this.update(refData.newValue); + if (refData.keyInComponents.length > 0) { + setValueInComponents( + refData.keyInComponents, + components, + refData.nodeContent + ); + } + } + } + }); }); - return components; } module.exports = { @@ -332,13 +387,18 @@ module.exports = { path = pathBrowserify; } let algorithm = new DFS(), - globalComponentsContext = {}; + globalComponentsContext = {}, + components = {}; algorithm.traverseAndBundle(specRoot, (currentNode) => { return getAdjacentAndMissingToBundle(currentNode, allData, specRoot, globalComponentsContext); }); - return generateComponentsObject(globalComponentsContext, specRoot.fileName); + generateComponentsObject(globalComponentsContext, specRoot.parsed.oasObject, isExtRef, components); + return { + fileContent: specRoot.parsed.oasObject, + components + }; }, bundleFiles: function(data) { @@ -351,7 +411,7 @@ module.exports = { Object.keys(bundleData).forEach((key) => { if (bundleData[key].hasOwnProperty('components')) { if (componentsFromFile) { - throw new Error('Muyltiple components definition through your files'); + throw new Error('Multiple components definition through your files'); } components = fillExistentComponents(bundleData.key.components, components); componentsFromFile = true; diff --git a/lib/dfs.js b/lib/dfs.js index 85d48de..2f443ae 100644 --- a/lib/dfs.js +++ b/lib/dfs.js @@ -30,7 +30,6 @@ class DFS { } traverseAndBundle(node, getAdjacentAndBundle) { - const mainNode = node; let traverseOrder = [], stack = [], missing = [], @@ -42,9 +41,9 @@ class DFS { if (!visited.has(node)) { traverseOrder.push(node); visited.add(node); - let { graphAdj, missingNodes } = getAdjacentAndBundle(node); + let { graphAdj, missingNodes, bundleDataInAdjacent } = getAdjacentAndBundle(node); missing.push(...missingNodes); - bundleData.push(bundleData); + bundleData.push(...bundleDataInAdjacent); for (let j = 0; j < graphAdj.length; j++) { stack.push(graphAdj[j]); } @@ -59,7 +58,7 @@ class DFS { ].map((str) => { return JSON.parse(str); }); - return { traverseOrder, missing, bundleData, mainNode }; + return { traverseOrder, missing, bundleData }; } } diff --git a/lib/jsonPointer.js b/lib/jsonPointer.js index 9194f97..38c3638 100644 --- a/lib/jsonPointer.js +++ b/lib/jsonPointer.js @@ -34,20 +34,13 @@ function jsonPointerDecodeAndReplace(filePathName) { * @param {string} localPath the local path that the pointer will reach * @returns {Array} - the calculated keys in an array representing each nesting property name */ -function getKeyInComponents(traceFromParent, filePathName) { +function getKeyInComponents(traceFromParent, filePathName, localPath) { + const localPart = localPath ? `#${localPath}` : ''; let res = traceFromParent; - res.push(jsonPointerDecodeAndReplace(filePathName)); - // TODOE: Add local support - // if (localPath) { - // if (localPath.startsWith(jsonPointerLevelSeparator)) { - // localPathToCheck = localPath.substring(1); - // } - // pointer = localPathToCheck.split(jsonPointerLevelSeparator); - // for (let i = 0; i < pointer.length; i++) { - // pointer[i] = jsonPointerDecodeAndReplace(pointer[i]); - // } - // res.push(...pointer); - // } + // .map((key) => { + // return key.split('#')[0]; + // }); + res.push(jsonPointerDecodeAndReplace(`${filePathName}${localPart}`)); return res; } @@ -72,18 +65,11 @@ function getLocalPath(jsonPointer) { * @param {string} localPath the local path that the pointer will reach * @returns {string} - the concatenated json pointer */ -function concatJsonPointer(encodeFunction, filePathName, traceFromParent, localPath) { - let local = '', - // base = '', - traceFromParentAsString = traceFromParent.map((trace) => { - return encodeFunction(trace); - }).join('/'); - // TODOE: local support - // base = jsonPointerLevelSeparator + encodeFunction(filePathName); - if (localPath) { - local = `${localPath}`; - } - return localPointer + jsonPointerLevelSeparator + traceFromParentAsString + local; +function concatJsonPointer(encodeFunction, traceFromParent) { + const traceFromParentAsString = traceFromParent.map((trace) => { + return encodeFunction(trace); + }).join('/'); + return localPointer + '/components' + jsonPointerLevelSeparator + traceFromParentAsString; } /** @@ -95,12 +81,12 @@ function concatJsonPointer(encodeFunction, filePathName, traceFromParent, localP * @param {string} traceFromParent the trace from the parent node. * @returns {string} - the concatenated json pointer */ -function getJsonPointerRelationToRoot(encodeFunction, filePathName, refValue, traceFromParent) { +function getJsonPointerRelationToRoot(encodeFunction, refValue, traceFromKey) { if (refValue.startsWith(localPointer)) { return refValue; } const localPath = getLocalPath(refValue); - return concatJsonPointer(encodeFunction, filePathName, traceFromParent, localPath); + return concatJsonPointer(encodeFunction, traceFromKey, localPath); } /** diff --git a/lib/schemaUtils.js b/lib/schemaUtils.js index b637e31..49edf5e 100644 --- a/lib/schemaUtils.js +++ b/lib/schemaUtils.js @@ -4826,13 +4826,15 @@ module.exports = { return data; }, - getComponentsObject(parsedRootFiles, inputData, origin, version) { + getBundledFileData(parsedRootFiles, inputData, origin, version) { const data = parsedRootFiles.map((root) => { let calledAs = [], - componentsData = getRelatedFilesAndBundleData(root, inputData, origin, version, calledAs); - return componentsData; + bundleData = getRelatedFilesAndBundleData(root, inputData, origin, version, calledAs); + return bundleData; }); - return data; + let bundledFile = data[0].fileContent; + bundledFile.components = data[0].components; + return { rootFile: { path: parsedRootFiles[0].fileName }, bundledContent: bundledFile }; }, /** @@ -4855,9 +4857,10 @@ module.exports = { return compareVersion(version, rootWithParsedContent.parsed.oasObject.openapi); }), data = toBundle ? - this.getComponentsObject(parsedRootFiles, inputData, origin, version) : + this.getBundledFileData(parsedRootFiles, inputData, origin, version) : this.getRelatedFilesData(parsedRootFiles, inputData, origin); return data; + }, /** @@ -4874,7 +4877,7 @@ module.exports = { res = { result: true, output: { - type: toBundle ? 'bundle' : 'relatedFiles', + type: toBundle ? 'bundledContent' : 'relatedFiles', specification: { type: 'OpenAPI', version: version diff --git a/test/data/toBundleExamples/local_ref/otherSchemas/toy.yaml b/test/data/toBundleExamples/local_ref/otherSchemas/toy.yaml new file mode 100644 index 0000000..9123b40 --- /dev/null +++ b/test/data/toBundleExamples/local_ref/otherSchemas/toy.yaml @@ -0,0 +1,6 @@ +type: object +properties: + id: + type: integer + toyName: + type: string \ No newline at end of file diff --git a/test/data/toBundleExamples/local_ref/responses.yaml b/test/data/toBundleExamples/local_ref/responses.yaml new file mode 100644 index 0000000..7541bc9 --- /dev/null +++ b/test/data/toBundleExamples/local_ref/responses.yaml @@ -0,0 +1,8 @@ +NotFound: + content: + application/json: + schema: + type: object + properties: + message: + type: string \ No newline at end of file diff --git a/test/data/toBundleExamples/local_ref/root.yaml b/test/data/toBundleExamples/local_ref/root.yaml new file mode 100644 index 0000000..b89a251 --- /dev/null +++ b/test/data/toBundleExamples/local_ref/root.yaml @@ -0,0 +1,50 @@ +openapi: 3.0.0 +info: + title: Sample API + description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. + version: 0.1.9 + +servers: + - url: http://api.example.com/v1 + description: Optional server description, e.g. Main (production) server + - url: http://staging-api.example.com + description: Optional server description, e.g. Internal staging server for testing + +paths: + /users/{userId}: + get: + summary: Get a user by ID + parameters: ... + responses: + 200: + description: A single user. + content: + application/json: + schema: + $ref: "#/components/schemas/User" + 404: + $ref: "#/components/responses/NotFound" + /users: + get: + summary: Get all users + responses: + 200: + description: A list of users. + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/User" + +components: + responses: + $ref: "./responses.yaml" + + schemas: + Monster: + $ref: "./schemas/index.yaml#/Monster" + Dog: + $ref: "./schemas/index.yaml#/Dog" + Toy: + $ref: "./otherSchemas/toy.yaml" \ No newline at end of file diff --git a/test/data/toBundleExamples/local_ref/schemas/client.yaml b/test/data/toBundleExamples/local_ref/schemas/client.yaml new file mode 100644 index 0000000..f72dfa3 --- /dev/null +++ b/test/data/toBundleExamples/local_ref/schemas/client.yaml @@ -0,0 +1,7 @@ +Client: + type: object + properties: + id: + type: integer + clientName: + type: string \ No newline at end of file diff --git a/test/data/toBundleExamples/local_ref/schemas/index.yaml b/test/data/toBundleExamples/local_ref/schemas/index.yaml new file mode 100644 index 0000000..6283e32 --- /dev/null +++ b/test/data/toBundleExamples/local_ref/schemas/index.yaml @@ -0,0 +1,21 @@ +User: + type: object + properties: + id: + type: integer + userName: + type: string +Monster: + type: object + properties: + id: + type: integer + clientName: + type: string +Dog: + type: object + properties: + id: + type: integer + clientName: + type: string \ No newline at end of file diff --git a/test/data/toBundleExamples/swagger-multi-file/schemas/index.yaml b/test/data/toBundleExamples/swagger-multi-file/schemas/index.yaml index 0c1e5c0..b68ca41 100644 --- a/test/data/toBundleExamples/swagger-multi-file/schemas/index.yaml +++ b/test/data/toBundleExamples/swagger-multi-file/schemas/index.yaml @@ -2,6 +2,13 @@ User: $ref: "./user.yaml" Monster: + type: object + properties: + id: + type: integer + clientName: + type: string +Dog: type: object properties: id: diff --git a/test/data/toBundleExamples/swagger-multi-file/v1.yaml b/test/data/toBundleExamples/swagger-multi-file/v1.yaml index 873d9e9..bdb46fc 100644 --- a/test/data/toBundleExamples/swagger-multi-file/v1.yaml +++ b/test/data/toBundleExamples/swagger-multi-file/v1.yaml @@ -42,6 +42,7 @@ components: $ref: "./responses.yaml" schemas: - $ref: "./schemas/index.yaml" + User: + $ref: "./schemas/index.yaml" Toy: $ref: "./otherSchemas/toy.yaml" \ No newline at end of file diff --git a/test/data/toBundleExamples/swagger-multi-file/v1_with_local_ref.yaml b/test/data/toBundleExamples/swagger-multi-file/v1_with_local_ref.yaml new file mode 100644 index 0000000..b89a251 --- /dev/null +++ b/test/data/toBundleExamples/swagger-multi-file/v1_with_local_ref.yaml @@ -0,0 +1,50 @@ +openapi: 3.0.0 +info: + title: Sample API + description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. + version: 0.1.9 + +servers: + - url: http://api.example.com/v1 + description: Optional server description, e.g. Main (production) server + - url: http://staging-api.example.com + description: Optional server description, e.g. Internal staging server for testing + +paths: + /users/{userId}: + get: + summary: Get a user by ID + parameters: ... + responses: + 200: + description: A single user. + content: + application/json: + schema: + $ref: "#/components/schemas/User" + 404: + $ref: "#/components/responses/NotFound" + /users: + get: + summary: Get all users + responses: + 200: + description: A list of users. + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/User" + +components: + responses: + $ref: "./responses.yaml" + + schemas: + Monster: + $ref: "./schemas/index.yaml#/Monster" + Dog: + $ref: "./schemas/index.yaml#/Dog" + Toy: + $ref: "./otherSchemas/toy.yaml" \ No newline at end of file diff --git a/test/data/toBundleExamples/swagger-multi-file_easy/root.yaml b/test/data/toBundleExamples/swagger-multi-file_easy/root.yaml new file mode 100644 index 0000000..3666bf2 --- /dev/null +++ b/test/data/toBundleExamples/swagger-multi-file_easy/root.yaml @@ -0,0 +1,23 @@ +openapi: 3.0.0 +info: + title: Sample API + description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML. + version: 0.1.9 + +servers: + - url: http://api.example.com/v1 + description: Optional server description, e.g. Main (production) server + - url: http://staging-api.example.com + description: Optional server description, e.g. Internal staging server for testing + +paths: + /users/{userId}: + get: + summary: Get a user by ID + responses: + 200: + description: A single user. + content: + application/json: + schema: + $ref: "./schemas/user.yaml" \ No newline at end of file diff --git a/test/data/toBundleExamples/swagger-multi-file_easy/schemas/user.yaml b/test/data/toBundleExamples/swagger-multi-file_easy/schemas/user.yaml new file mode 100644 index 0000000..81370de --- /dev/null +++ b/test/data/toBundleExamples/swagger-multi-file_easy/schemas/user.yaml @@ -0,0 +1,6 @@ +type: object +properties: + id: + type: integer + userName: + type: string \ No newline at end of file diff --git a/test/unit/bundle.test.js b/test/unit/bundle.test.js index ddb79cb..f2bab7b 100644 --- a/test/unit/bundle.test.js +++ b/test/unit/bundle.test.js @@ -3,18 +3,48 @@ let expect = require('chai').expect, fs = require('fs'), path = require('path'), BUNDLES_FOLDER = '../data/toBundleExamples', - swaggerToBundleFolder = path.join(__dirname, BUNDLES_FOLDER + '/swagger-multi-file'); + swaggerMultifileFolder = path.join(__dirname, BUNDLES_FOLDER + '/swagger-multi-file'), + localRefFolder = path.join(__dirname, BUNDLES_FOLDER + '/local_ref'), + easyFolder = path.join(__dirname, BUNDLES_FOLDER + '/swagger-multi-file_easy'); -describe('detectRelatedFiles method', function () { +describe('bundle files method', function () { + it('Should return bundled file with an schema called from a response', async function () { + let contentRootFile = fs.readFileSync(easyFolder + '/root.yaml', 'utf8'), + user = fs.readFileSync(easyFolder + '/schemas/user.yaml', 'utf8'), + input = { + type: 'folder', + specificationVersion: '3.0', + rootFiles: [ + { + path: '/root.yaml', + content: contentRootFile + } + ], + data: [ + { + path: '/schemas/user.yaml', + content: user + } + ] + }; + const res = await Converter.bundle(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + expect(res.output.data.bundledContent.paths['/users/{userId}'].get.responses['200'] + .content['application/json'].schema.$ref) + .to.be.equal('#/components/schema/~1schemas~1user.yaml'); + expect(Object.keys(res.output.data.bundledContent.components.schemas['/schemas/user.yaml'])) + .to.have.members(['type', 'properties']); + }); - it('Should return bundled file', async function () { - let contentRootFile = fs.readFileSync(swaggerToBundleFolder + '/v1.yaml', 'utf8'), - responses = fs.readFileSync(swaggerToBundleFolder + '/responses.yaml', 'utf8'), - schemasIndex = fs.readFileSync(swaggerToBundleFolder + '/schemas/index.yaml', 'utf8'), - schemasUser = fs.readFileSync(swaggerToBundleFolder + '/schemas/user.yaml', 'utf8'), - schemasClient = fs.readFileSync(swaggerToBundleFolder + '/schemas/client.yaml', 'utf8'), - toySchema = fs.readFileSync(swaggerToBundleFolder + '/otherSchemas/toy.yaml', 'utf8'), - userProps = fs.readFileSync(swaggerToBundleFolder + '/userProps.yaml', 'utf8'), + it('Should return bundled file from root with components with', async function () { + let contentRootFile = fs.readFileSync(swaggerMultifileFolder + '/v1.yaml', 'utf8'), + responses = fs.readFileSync(swaggerMultifileFolder + '/responses.yaml', 'utf8'), + schemasIndex = fs.readFileSync(swaggerMultifileFolder + '/schemas/index.yaml', 'utf8'), + schemasUser = fs.readFileSync(swaggerMultifileFolder + '/schemas/user.yaml', 'utf8'), + schemasClient = fs.readFileSync(swaggerMultifileFolder + '/schemas/client.yaml', 'utf8'), + toySchema = fs.readFileSync(swaggerMultifileFolder + '/otherSchemas/toy.yaml', 'utf8'), + userProps = fs.readFileSync(swaggerMultifileFolder + '/userProps.yaml', 'utf8'), input = { type: 'folder', specificationVersion: '3.0', @@ -55,4 +85,43 @@ describe('detectRelatedFiles method', function () { expect(res).to.not.be.empty; expect(res.result).to.be.true; }); + + it('Should return bundled file from a file with local references', async function () { + let contentRootFile = fs.readFileSync(localRefFolder + '/root.yaml', 'utf8'), + responses = fs.readFileSync(localRefFolder + '/responses.yaml', 'utf8'), + schemasIndex = fs.readFileSync(localRefFolder + '/schemas/index.yaml', 'utf8'), + schemasClient = fs.readFileSync(localRefFolder + '/schemas/client.yaml', 'utf8'), + toySchema = fs.readFileSync(localRefFolder + '/otherSchemas/toy.yaml', 'utf8'), + input = { + type: 'folder', + specificationVersion: '3.0', + rootFiles: [ + { + path: '/root.yaml', + content: contentRootFile + } + ], + data: [ + { + path: '/responses.yaml', + content: responses + }, + { + path: '/schemas/index.yaml', + content: schemasIndex + }, + { + path: '/schemas/client.yaml', + content: schemasClient + }, + { + path: '/otherSchemas/toy.yaml', + content: toySchema + } + ] + }; + const res = await Converter.bundle(input); + expect(res).to.not.be.empty; + expect(res.result).to.be.true; + }); }); diff --git a/test/unit/jsonPointer.test.js b/test/unit/jsonPointer.test.js index 8b02d67..bdacd6e 100644 --- a/test/unit/jsonPointer.test.js +++ b/test/unit/jsonPointer.test.js @@ -14,17 +14,14 @@ describe('getKeyInComponents function', function () { expect(result[2]).to.equal('pet.yaml'); }); - // TODOE: support this test - // it('should return ["components", "schemas", "pet.yaml", "definitions", "world"] when is pointing to a local ref', - // function () { - // const result = getKeyInComponents(['components', 'schemas'], 'pet.yaml', '/definitions/world'); - // expect(result.length).to.equal(5); - // expect(result[0]).to.equal('components'); - // expect(result[1]).to.equal('schemas'); - // expect(result[2]).to.equal('pet.yaml'); - // expect(result[3]).to.equal('definitions'); - // expect(result[4]).to.equal('world'); - // }); + it('should return ["components", "schemas", "pet.yaml", "definitions", "world"] when is pointing to a local ref', + function () { + const result = getKeyInComponents(['components', 'schemas'], 'pet.yaml', '/definitions/world'); + expect(result.length).to.equal(3); + expect(result[0]).to.equal('components'); + expect(result[1]).to.equal('schemas'); + expect(result[2]).to.equal('pet.yaml#/definitions/world'); + }); it('should return ["components", "schemas", "folder/pet.yaml"] when there is an scaped slash', function () { const result = getKeyInComponents(['components', 'schemas'], 'folder~1pet.yaml'); @@ -41,26 +38,23 @@ describe('getJsonPointerRelationToRoot function', function () { let res = getJsonPointerRelationToRoot( jsonPointerEncodeAndReplace, 'Pets.yaml', - 'Pets.yaml', - ['components', 'schemas', 'Pets.yaml'] + ['schemas', 'Pets.yaml'] ); expect(res).to.equal('#/components/schemas/Pets.yaml'); }); - it('should return "#/components/schemas/hello.yaml/definitions/world" no local path and schema', function () { + it('should return "#/components/schemas/hello.yaml" no local path and schema', function () { let res = getJsonPointerRelationToRoot( jsonPointerEncodeAndReplace, - 'hello.yaml', 'hello.yaml#/definitions/world', - ['components', 'schemas', 'hello.yaml'] + ['schemas', 'hello.yaml'] ); - expect(res).to.equal('#/components/schemas/hello.yaml/definitions/world'); + expect(res).to.equal('#/components/schemas/hello.yaml'); }); it('should return "#/components/schemas/Error" no file path', function () { let res = getJsonPointerRelationToRoot( jsonPointerEncodeAndReplace, - '', '#/components/schemas/Error', - ['components', 'schemas'] + ['components', 'schemas', 'Error'] ); expect(res).to.equal('#/components/schemas/Error'); }); @@ -70,8 +64,7 @@ describe('concatJsonPointer function ', function () { it('should return "#/components/schemas/Pets.yaml" no local path and schema', function () { let res = concatJsonPointer( jsonPointerEncodeAndReplace, - 'Pets.yaml', - ['components', 'schemas', 'Pets.yaml'] + ['schemas', 'Pets.yaml'] ); expect(res).to.equal('#/components/schemas/Pets.yaml'); }); @@ -79,34 +72,29 @@ describe('concatJsonPointer function ', function () { it('should return "#/components/schemas/other~1Pets.yaml" no local path and schema folder in filename', function () { let res = concatJsonPointer( jsonPointerEncodeAndReplace, - 'other/Pets.yaml', - ['components', 'schemas', 'other/Pets.yaml'] + ['schemas', 'other/Pets.yaml'] ); expect(res).to.equal('#/components/schemas/other~1Pets.yaml'); }); it('should return "#/components/schemas/some~1Pet" no local path and schema folder in filename', function () { let res = concatJsonPointer( jsonPointerEncodeAndReplace, - 'some/Pet.yaml', - ['components', 'schemas', 'some/Pet.yaml'] + ['schemas', 'some/Pet.yaml'] ); expect(res).to.equal('#/components/schemas/some~1Pet.yaml'); }); - it('should return "#/components/schemas/hello.yaml/definitions/world" no local path and schema', function () { + it('should return "#/components/schemas/hello.yaml" no local path and schema', function () { let res = concatJsonPointer( jsonPointerEncodeAndReplace, - 'hello.yaml', - ['components', 'schemas', 'hello.yaml'], - '/definitions/world' + ['schemas', 'hello.yaml'] ); - expect(res).to.equal('#/components/schemas/hello.yaml/definitions/world'); + expect(res).to.equal('#/components/schemas/hello.yaml'); }); it('should return "#/components/schemas/~1Pets.yaml" no local path and schema', function () { let res = concatJsonPointer( jsonPointerEncodeAndReplace, - '/Pets.yaml', - ['components', 'schemas', '/Pets.yaml'] + ['schemas', '/Pets.yaml'] ); expect(res).to.equal('#/components/schemas/~1Pets.yaml'); });