mirror of
https://github.com/postmanlabs/openapi-to-postman.git
synced 2022-11-29 22:05:00 +03:00
Adding components generation
This commit is contained in:
5
index.js
5
index.js
@@ -42,6 +42,11 @@ module.exports = {
|
||||
return schema.detectRelatedFiles();
|
||||
},
|
||||
|
||||
bundle: async function(input) {
|
||||
var schema = new SchemaPack(input);
|
||||
return schema.bundle();
|
||||
},
|
||||
|
||||
// new API
|
||||
SchemaPack
|
||||
};
|
||||
|
||||
@@ -9,11 +9,12 @@ module.exports = {
|
||||
/**
|
||||
* Parses an OAS string/object as a YAML or JSON
|
||||
* @param {YAML/JSON} openApiSpec - The OAS 3.x specification specified in either YAML or JSON
|
||||
* @param {object} options - The parsing options
|
||||
* @returns {Object} - Contains the parsed JSON-version of the OAS spec, or an error
|
||||
* @no-unit-test
|
||||
*/
|
||||
parseSpec: function (openApiSpec) {
|
||||
return schemaUtilsCommon.parseSpec(openApiSpec, inputValidation30X);
|
||||
parseSpec: function (openApiSpec, options) {
|
||||
return schemaUtilsCommon.parseSpec(openApiSpec, inputValidation30X, options);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
366
lib/bundle.js
Normal file
366
lib/bundle.js
Normal file
@@ -0,0 +1,366 @@
|
||||
const {
|
||||
isExtRef,
|
||||
getKeyInComponents,
|
||||
getJsonPointerRelationToRoot,
|
||||
jsonPointerEncodeAndReplace
|
||||
} = require('./jsonPointer'),
|
||||
traverseUtility = require('traverse'),
|
||||
parse = require('./parse.js');
|
||||
|
||||
let path = require('path'),
|
||||
pathBrowserify = require('path-browserify'),
|
||||
BROWSER = 'browser',
|
||||
{ DFS } = require('./dfs');
|
||||
|
||||
|
||||
/**
|
||||
* Locates a referenced node from the data input by path
|
||||
* @param {string} path1 - path1 to compare
|
||||
* @param {string} path2 - path2 to compare
|
||||
* @returns {boolean} - wheter is the same path
|
||||
*/
|
||||
function comparePaths(path1, path2) {
|
||||
return path1 === path2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the local pointer inside a path
|
||||
* aab.yaml#component returns aab.yaml
|
||||
* @param {string} refValue - value of the $ref property
|
||||
* @returns {string} - the calculated path only
|
||||
*/
|
||||
function removeLocalReferenceFromPath(refValue) {
|
||||
if (refValue.$ref.includes('#')) {
|
||||
return refValue.$ref.split('#')[0];
|
||||
}
|
||||
return refValue.$ref;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the path relative to parent
|
||||
* @param {string} parentFileName - parent file name of the current node
|
||||
* @param {string} referencePath - value of the $ref property
|
||||
* @returns {object} - Detect root files result object
|
||||
*/
|
||||
function calculatePath(parentFileName, referencePath) {
|
||||
let currentDirName = path.dirname(parentFileName),
|
||||
refDirName = path.join(currentDirName, referencePath);
|
||||
return refDirName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates a referenced node from the data input by path
|
||||
* @param {string} referencePath - value from the $ref property
|
||||
* @param {Array} allData - array of { path, content} objects
|
||||
* @returns {object} - Detect root files result object
|
||||
*/
|
||||
function findNodeFromPath(referencePath, allData) {
|
||||
const partialComponents = referencePath.split('#');
|
||||
let isPartial = partialComponents.length > 1,
|
||||
node = allData.find((node) => {
|
||||
if (isPartial) {
|
||||
referencePath = partialComponents[0];
|
||||
}
|
||||
return comparePaths(node.fileName, referencePath);
|
||||
});
|
||||
if (node) {
|
||||
node.isPartial = isPartial;
|
||||
node.partialCalled = partialComponents[1];
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the path relative to parent
|
||||
* @param {string} parentFileName - parent file name of the current node
|
||||
* @param {string} referencePath - value of the $ref property
|
||||
* @returns {object} - Detect root files result object
|
||||
*/
|
||||
function calculatePathMissing(parentFileName, referencePath) {
|
||||
let currentDirName = path.dirname(parentFileName),
|
||||
refDirName = path.join(currentDirName, referencePath);
|
||||
if (refDirName.startsWith('..' + path.sep)) {
|
||||
return { path: undefined, $ref: referencePath };
|
||||
}
|
||||
else if (path.isAbsolute(parentFileName) && !path.isAbsolute(referencePath)) {
|
||||
let relativeToRoot = path.join(currentDirName.replace(path.sep, ''), referencePath);
|
||||
if (relativeToRoot.startsWith('..' + path.sep)) {
|
||||
return { path: undefined, $ref: referencePath };
|
||||
}
|
||||
}
|
||||
return { path: refDirName, $ref: undefined };
|
||||
}
|
||||
|
||||
/**
|
||||
* verifies if the path has been added to the result
|
||||
* @param {string} path - path to find
|
||||
* @param {Array} referencesInNode - Array with the already added paths
|
||||
* @returns {boolean} - wheter a node with the same path has been added
|
||||
*/
|
||||
function added(path, referencesInNode) {
|
||||
return referencesInNode.find((reference) => { return reference.path === path; }) !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a trace from the first parent node name attachable in components object
|
||||
* @param {array} nodeParents - The parent node's name from the current node
|
||||
* @returns {array} A trace from the first node name attachable in components object
|
||||
*/
|
||||
function getRootFileTrace(nodeParents) {
|
||||
let trace = [];
|
||||
for (let parentKey of nodeParents) {
|
||||
if ([undefined, 'oasObject'].includes(parentKey)) {
|
||||
break;
|
||||
}
|
||||
trace.push(parentKey);
|
||||
}
|
||||
return trace.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
function getTraceFromParent(nodeTrace, connector) {
|
||||
return connector.concat(nodeTrace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the $refs from an object
|
||||
* @param {object} currentNode - current node in process
|
||||
* @param {Function} refTypeResolver - function to resolve the ref according to type (local, external, web etc)
|
||||
* @param {Function} pathSolver - function to resolve the Path
|
||||
* @param {string} parentFilename - The parent's filename
|
||||
* @param {object} globalComponentsContext - The global context from root file
|
||||
* @returns {object} - {path : $ref value}
|
||||
*/
|
||||
function getReferences (currentNode, refTypeResolver, pathSolver, parentFilename, globalComponentsContext) {
|
||||
let referencesInNode = [];
|
||||
|
||||
traverseUtility(currentNode).forEach(function (property) {
|
||||
if (property) {
|
||||
let hasReferenceTypeKey;
|
||||
hasReferenceTypeKey = Object.keys(property)
|
||||
.find(
|
||||
(key) => {
|
||||
return refTypeResolver(property, key);
|
||||
}
|
||||
);
|
||||
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),
|
||||
referenceInDocument = getJsonPointerRelationToRoot(
|
||||
jsonPointerEncodeAndReplace,
|
||||
file,
|
||||
property.$ref,
|
||||
traceFromParent
|
||||
);
|
||||
|
||||
newValue.$ref = referenceInDocument;
|
||||
this.update(newValue);
|
||||
|
||||
if (globalComponentsContext[file]) {
|
||||
globalComponentsContext[file].isFull =
|
||||
globalComponentsContext[file].isFull && !local;
|
||||
if (local) {
|
||||
globalComponentsContext[file].partialCalled.push(local);
|
||||
}
|
||||
}
|
||||
else {
|
||||
globalComponentsContext[file] = {
|
||||
calledFrom: parentFilename,
|
||||
connector: keyInComponents,
|
||||
isFull: !local,
|
||||
partialsCalled: local ? [local] : [],
|
||||
referenceInDocument,
|
||||
content: this.node
|
||||
};
|
||||
}
|
||||
if (!added(property.$ref, referencesInNode)) {
|
||||
referencesInNode.push({ path: pathSolver(property), keyInComponents });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (globalComponentsContext[parentFilename]) {
|
||||
globalComponentsContext[parentFilename].content = currentNode.oasObject;
|
||||
}
|
||||
else {
|
||||
globalComponentsContext[parentFilename] = {
|
||||
isRoot: true,
|
||||
filename: parentFilename,
|
||||
content: currentNode.oasObject
|
||||
};
|
||||
}
|
||||
return referencesInNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the output from get root files to detect root files
|
||||
* @param {object} currentNode - current { path, content} object
|
||||
* @param {Array} allData - array of { path, content} objects
|
||||
* @param {object} specRoot - root file information
|
||||
* @param {string} globalComponentsContext - the context from the global level
|
||||
* @returns {object} - Detect root files result object
|
||||
*/
|
||||
function getAdjacentAndMissingToBundle (currentNode, allData, specRoot, globalComponentsContext) {
|
||||
let currentNodeReferences,
|
||||
currentContent = currentNode.content,
|
||||
graphAdj = [],
|
||||
missingNodes = [],
|
||||
bundleDataInAdjacent = [],
|
||||
OASObject;
|
||||
|
||||
if (currentContent.parsed) {
|
||||
OASObject = currentNode.parsed;
|
||||
}
|
||||
else {
|
||||
OASObject = parse.getOasObject(currentContent);
|
||||
}
|
||||
|
||||
currentNodeReferences = getReferences(
|
||||
OASObject,
|
||||
isExtRef,
|
||||
removeLocalReferenceFromPath,
|
||||
currentNode.fileName,
|
||||
globalComponentsContext
|
||||
);
|
||||
|
||||
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)) {
|
||||
let calculatedPathForMissing = calculatePathMissing(currentNode.fileName, referencePath);
|
||||
if (!calculatedPathForMissing.$ref) {
|
||||
missingNodes.push({ path: calculatedPathForMissing.path });
|
||||
}
|
||||
else {
|
||||
missingNodes.push({ $ref: calculatedPathForMissing.$ref, path: null });
|
||||
}
|
||||
}
|
||||
});
|
||||
if (missingNodes.length > 0) {
|
||||
throw new Error('Some files are missing, run detectRelatedFiles to get more detail');
|
||||
}
|
||||
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
|
||||
* @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);
|
||||
});
|
||||
return components;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Maps the output from get root files to detect root files
|
||||
* @param {object} specRoot - root file information
|
||||
* @param {Array} allData - array of { path, content} objects
|
||||
* @param {Array} origin - process origin (BROWSER or node)
|
||||
* @returns {object} - Detect root files result object
|
||||
*/
|
||||
getRelatedFilesAndBundleData: function (specRoot, allData, origin) {
|
||||
if (origin === BROWSER) {
|
||||
path = pathBrowserify;
|
||||
}
|
||||
let algorithm = new DFS(),
|
||||
globalComponentsContext = {};
|
||||
|
||||
algorithm.traverseAndBundle(specRoot, (currentNode) => {
|
||||
return getAdjacentAndMissingToBundle(currentNode, allData, specRoot, globalComponentsContext);
|
||||
});
|
||||
|
||||
return generateComponentsObject(globalComponentsContext, specRoot.fileName);
|
||||
},
|
||||
|
||||
bundleFiles: function(data) {
|
||||
let { bundleData, missingRelatedFiles } = data[0],
|
||||
components = {},
|
||||
componentsFromFile = false;
|
||||
if (missingRelatedFiles.length > 0) {
|
||||
throw new Error(`There are ${missingRelatedFiles.length} missing files in yopur spec`);
|
||||
}
|
||||
Object.keys(bundleData).forEach((key) => {
|
||||
if (bundleData[key].hasOwnProperty('components')) {
|
||||
if (componentsFromFile) {
|
||||
throw new Error('Muyltiple components definition through your files');
|
||||
}
|
||||
components = fillExistentComponents(bundleData.key.components, components);
|
||||
componentsFromFile = true;
|
||||
}
|
||||
else {
|
||||
components[key] = bundleData[key].content;
|
||||
}
|
||||
});
|
||||
return components;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -229,6 +229,9 @@ module.exports = {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
if (options && options.partial) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
// spec is a valid JSON object at this point
|
||||
|
||||
|
||||
33
lib/dfs.js
33
lib/dfs.js
@@ -28,6 +28,39 @@ class DFS {
|
||||
});
|
||||
return { traverseOrder, missing };
|
||||
}
|
||||
|
||||
traverseAndBundle(node, getAdjacentAndBundle) {
|
||||
const mainNode = node;
|
||||
let traverseOrder = [],
|
||||
stack = [],
|
||||
missing = [],
|
||||
visited = new Set(),
|
||||
bundleData = [];
|
||||
stack.push(node);
|
||||
while (stack.length > 0) {
|
||||
node = stack.pop();
|
||||
if (!visited.has(node)) {
|
||||
traverseOrder.push(node);
|
||||
visited.add(node);
|
||||
let { graphAdj, missingNodes } = getAdjacentAndBundle(node);
|
||||
missing.push(...missingNodes);
|
||||
bundleData.push(bundleData);
|
||||
for (let j = 0; j < graphAdj.length; j++) {
|
||||
stack.push(graphAdj[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
missing = [
|
||||
...new Set(
|
||||
missing.map((obj) => {
|
||||
return JSON.stringify(obj);
|
||||
})
|
||||
)
|
||||
].map((str) => {
|
||||
return JSON.parse(str);
|
||||
});
|
||||
return { traverseOrder, missing, bundleData, mainNode };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -2,7 +2,6 @@ const slashes = /\//g,
|
||||
tildes = /~/g,
|
||||
escapedSlash = /~1/g,
|
||||
escapedSlashString = '~1',
|
||||
componentsKey = 'components',
|
||||
localPointer = '#',
|
||||
escapedTilde = /~0/g,
|
||||
jsonPointerLevelSeparator = '/',
|
||||
@@ -30,27 +29,25 @@ function jsonPointerDecodeAndReplace(filePathName) {
|
||||
|
||||
/**
|
||||
* returns the key that the object in components will have could be nested
|
||||
* @param {string} componentName the type of component e.g. schemas, parameters, etc.
|
||||
* @param {string} traceFromParent the node trace from root.
|
||||
* @param {string} filePathName the filePathName of the file
|
||||
* @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(componentName, filePathName, localPath) {
|
||||
let res = [componentsKey],
|
||||
pointer,
|
||||
localPathToCheck = localPath;
|
||||
res.push(componentName);
|
||||
function getKeyInComponents(traceFromParent, filePathName) {
|
||||
let res = traceFromParent;
|
||||
res.push(jsonPointerDecodeAndReplace(filePathName));
|
||||
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);
|
||||
}
|
||||
// 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);
|
||||
// }
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -71,19 +68,22 @@ function getLocalPath(jsonPointer) {
|
||||
* @constructor
|
||||
* @param {Function} encodeFunction function to encode url
|
||||
* @param {string} filePathName the filePathName of the file
|
||||
* @param {string} componentName the type of component e.g. schemas, parameters, etc.
|
||||
* @param {string} traceFromParent the trace from parent.
|
||||
* @param {string} localPath the local path that the pointer will reach
|
||||
* @returns {string} - the concatenated json pointer
|
||||
*/
|
||||
function concatJsonPointer(encodeFunction, filePathName, componentName, localPath) {
|
||||
let base = '',
|
||||
local = '';
|
||||
base = jsonPointerLevelSeparator + encodeFunction(filePathName);
|
||||
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;
|
||||
local = `${localPath}`;
|
||||
}
|
||||
return localPointer + jsonPointerLevelSeparator + componentsKey +
|
||||
jsonPointerLevelSeparator + componentName + base + local;
|
||||
return localPointer + jsonPointerLevelSeparator + traceFromParentAsString + local;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,15 +92,15 @@ function concatJsonPointer(encodeFunction, filePathName, componentName, localPat
|
||||
* @param {Function} encodeFunction function to encode url
|
||||
* @param {string} filePathName the filePathName of the file
|
||||
* @param {string} refValue the type of component e.g. schemas, parameters, etc.
|
||||
* @param {string} componentName the type of component e.g. schemas, parameters, etc.
|
||||
* @param {string} traceFromParent the trace from the parent node.
|
||||
* @returns {string} - the concatenated json pointer
|
||||
*/
|
||||
function getJsonPointerRelationToRoot(encodeFunction, filePathName, refValue, componentName) {
|
||||
function getJsonPointerRelationToRoot(encodeFunction, filePathName, refValue, traceFromParent) {
|
||||
if (refValue.startsWith(localPointer)) {
|
||||
return refValue;
|
||||
}
|
||||
const localPath = getLocalPath(refValue);
|
||||
return concatJsonPointer(encodeFunction, filePathName, componentName, localPath);
|
||||
return concatJsonPointer(encodeFunction, filePathName, traceFromParent, localPath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -89,7 +89,8 @@ const { formatDataPath, checkIsCorrectType, isKnownType } = require('./common/sc
|
||||
DEFAULT_SCHEMA_UTILS = require('./30XUtils/schemaUtils30X'),
|
||||
{ getRelatedFiles } = require('./relatedFiles'),
|
||||
{ compareVersion } = require('./common/versionUtils.js'),
|
||||
parse = require('./parse');
|
||||
parse = require('./parse'),
|
||||
{ getRelatedFilesAndBundleData } = require('./bundle.js');
|
||||
/* eslint-enable */
|
||||
|
||||
// See https://github.com/json-schema-faker/json-schema-faker/tree/master/docs#available-options
|
||||
@@ -4812,6 +4813,28 @@ module.exports = {
|
||||
};
|
||||
},
|
||||
|
||||
getRelatedFilesData(parsedRootFiles, inputData, origin) {
|
||||
const data = parsedRootFiles.map((root) => {
|
||||
let relatedData = getRelatedFiles(root, inputData, origin),
|
||||
result = {
|
||||
rootFile: { path: root.fileName },
|
||||
relatedFiles: relatedData.relatedFiles,
|
||||
missingRelatedFiles: relatedData.missingRelatedFiles
|
||||
};
|
||||
return result;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
getComponentsObject(parsedRootFiles, inputData, origin, version) {
|
||||
const data = parsedRootFiles.map((root) => {
|
||||
let calledAs = [],
|
||||
componentsData = getRelatedFilesAndBundleData(root, inputData, origin, version, calledAs);
|
||||
return componentsData;
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Takes in root files, input data and origin process every root
|
||||
@@ -4820,24 +4843,20 @@ module.exports = {
|
||||
* @param {array} inputData - [{path:string}]}
|
||||
* @param {string} origin - process origin
|
||||
* @param {string} version - process specification version
|
||||
* @param {boolean} toBundle - if it will be used in bundle
|
||||
*
|
||||
* @returns {object} root files information and data input
|
||||
*/
|
||||
mapProcessRelatedFiles(rootFiles, inputData, origin, version) {
|
||||
mapProcessRelatedFiles(rootFiles, inputData, origin, version, toBundle = false) {
|
||||
let parsedRootFiles = rootFiles.map((rootFile) => {
|
||||
let parsedContent = parse.getOasObject(rootFile.content);
|
||||
return { fileName: rootFile.fileName, content: rootFile.content, parsed: parsedContent };
|
||||
}).filter((rootWithParsedContent) => {
|
||||
return compareVersion(version, rootWithParsedContent.parsed.oasObject.openapi);
|
||||
}),
|
||||
data = parsedRootFiles.map((root) => {
|
||||
let { relatedFiles, missingRelatedFiles } = getRelatedFiles(root, inputData, origin);
|
||||
return {
|
||||
rootFile: { path: root.fileName },
|
||||
relatedFiles,
|
||||
missingRelatedFiles
|
||||
};
|
||||
});
|
||||
data = toBundle ?
|
||||
this.getComponentsObject(parsedRootFiles, inputData, origin, version) :
|
||||
this.getRelatedFilesData(parsedRootFiles, inputData, origin);
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -4846,15 +4865,16 @@ module.exports = {
|
||||
* @description Takes in a folder and identifies the related files from the
|
||||
* root file perspective (using $ref property)
|
||||
* @param {string} inputRelatedFiles - {rootFile:{path:string}, data: [{path:string}]}
|
||||
* @param {boolean} toBundle - if true it will return the bundle data
|
||||
*
|
||||
* @returns {object} root files information and data input
|
||||
*/
|
||||
processRelatedFiles(inputRelatedFiles) {
|
||||
processRelatedFiles(inputRelatedFiles, toBundle = false) {
|
||||
let version = inputRelatedFiles.specificationVersion ? inputRelatedFiles.specificationVersion : '3.0.0',
|
||||
res = {
|
||||
result: true,
|
||||
output: {
|
||||
type: 'relatedFiles',
|
||||
type: toBundle ? 'bundle' : 'relatedFiles',
|
||||
specification: {
|
||||
type: 'OpenAPI',
|
||||
version: version
|
||||
@@ -4865,9 +4885,12 @@ module.exports = {
|
||||
};
|
||||
if (inputRelatedFiles.rootFiles && inputRelatedFiles.rootFiles.length > 0) {
|
||||
res.output.data = this.mapProcessRelatedFiles(inputRelatedFiles.rootFiles, inputRelatedFiles.data,
|
||||
inputRelatedFiles.origin, version);
|
||||
inputRelatedFiles.origin, version, toBundle);
|
||||
return res;
|
||||
}
|
||||
else {
|
||||
return res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
// const { traverseToBundle } = require('./bundle.js');
|
||||
|
||||
// This is the default collection name if one can't be inferred from the OpenAPI spec
|
||||
const COLLECTION_NAME = 'Imported from OpenAPI 3.0',
|
||||
{ getConcreteSchemaUtils } = require('./common/versionUtils.js'),
|
||||
{ getConcreteSchemaUtils/* , getConcreteUtilsFromVersion*/ } = require('./common/versionUtils.js'),
|
||||
{ convertSwaggerToOpenapi } = require('./swaggerUtils/swaggerToOpenapi.js'),
|
||||
BROWSER = 'browser',
|
||||
Ajv = require('ajv'),
|
||||
@@ -30,6 +32,7 @@ const COLLECTION_NAME = 'Imported from OpenAPI 3.0',
|
||||
let path = require('path'),
|
||||
concreteUtils,
|
||||
pathBrowserify = require('path-browserify');
|
||||
// traverseUtility = require('traverse');
|
||||
|
||||
class SchemaPack {
|
||||
constructor (input, options = {}) {
|
||||
@@ -682,6 +685,36 @@ class SchemaPack {
|
||||
input.rootFiles = adaptedRootFiles;
|
||||
return schemaUtils.processRelatedFiles(input);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @description Takes in a folder and identifies the related files from the
|
||||
* root file perspective (using $ref property)
|
||||
*
|
||||
* @returns {object} root files information and data input
|
||||
*/
|
||||
async bundle() {
|
||||
const input = this.input;
|
||||
if (!input.rootFiles || input.rootFiles.length === 0) {
|
||||
let rootFiles = await this.detectRootFiles(input);
|
||||
if (rootFiles.output.data) {
|
||||
let inputContent = [];
|
||||
rootFiles.output.data.forEach((rootFile) => {
|
||||
let founInData = input.data.find((dataFile) => { return dataFile.fileName === rootFile.path; });
|
||||
if (founInData) {
|
||||
inputContent.push({ fileName: founInData.fileName, content: founInData.content });
|
||||
}
|
||||
});
|
||||
input.rootFiles = inputContent;
|
||||
return schemaUtils.processRelatedFiles(input, true);
|
||||
}
|
||||
}
|
||||
let adaptedRootFiles = input.rootFiles.map((rootFile) => {
|
||||
return { fileName: rootFile.path, content: rootFile.content };
|
||||
});
|
||||
input.rootFiles = adaptedRootFiles;
|
||||
return schemaUtils.processRelatedFiles(input, true);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
toyName:
|
||||
type: string
|
||||
@@ -0,0 +1,8 @@
|
||||
NotFound:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
@@ -0,0 +1,7 @@
|
||||
Client:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
clientName:
|
||||
type: string
|
||||
@@ -0,0 +1,10 @@
|
||||
User:
|
||||
$ref: "./user.yaml"
|
||||
|
||||
Monster:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
clientName:
|
||||
type: string
|
||||
@@ -0,0 +1,3 @@
|
||||
type: object
|
||||
properties:
|
||||
$ref: '../userProps.yaml'
|
||||
@@ -0,0 +1,4 @@
|
||||
id:
|
||||
type: integer
|
||||
userName:
|
||||
type: string
|
||||
47
test/data/toBundleExamples/swagger-multi-file/v1.yaml
Normal file
47
test/data/toBundleExamples/swagger-multi-file/v1.yaml
Normal file
@@ -0,0 +1,47 @@
|
||||
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:
|
||||
$ref: "./schemas/index.yaml"
|
||||
Toy:
|
||||
$ref: "./otherSchemas/toy.yaml"
|
||||
58
test/unit/bundle.test.js
Normal file
58
test/unit/bundle.test.js
Normal file
@@ -0,0 +1,58 @@
|
||||
let expect = require('chai').expect,
|
||||
Converter = require('../../index.js'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
BUNDLES_FOLDER = '../data/toBundleExamples',
|
||||
swaggerToBundleFolder = path.join(__dirname, BUNDLES_FOLDER + '/swagger-multi-file');
|
||||
|
||||
describe('detectRelatedFiles method', function () {
|
||||
|
||||
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'),
|
||||
input = {
|
||||
type: 'folder',
|
||||
specificationVersion: '3.0',
|
||||
rootFiles: [
|
||||
{
|
||||
path: '/v1.yaml',
|
||||
content: contentRootFile
|
||||
}
|
||||
],
|
||||
data: [
|
||||
{
|
||||
path: '/responses.yaml',
|
||||
content: responses
|
||||
},
|
||||
{
|
||||
path: '/schemas/index.yaml',
|
||||
content: schemasIndex
|
||||
},
|
||||
{
|
||||
path: '/schemas/client.yaml',
|
||||
content: schemasClient
|
||||
},
|
||||
{
|
||||
path: '/schemas/user.yaml',
|
||||
content: schemasUser
|
||||
},
|
||||
{
|
||||
path: '/otherSchemas/toy.yaml',
|
||||
content: toySchema
|
||||
},
|
||||
{
|
||||
path: '/userProps.yaml',
|
||||
content: userProps
|
||||
}
|
||||
]
|
||||
};
|
||||
const res = await Converter.bundle(input);
|
||||
expect(res).to.not.be.empty;
|
||||
expect(res.result).to.be.true;
|
||||
});
|
||||
});
|
||||
@@ -7,26 +7,27 @@ const expect = require('chai').expect,
|
||||
|
||||
describe('getKeyInComponents function', function () {
|
||||
it('should return ["components", "schemas", "pet.yaml"] when is pointing the entire file', function () {
|
||||
const result = getKeyInComponents('schemas', 'pet.yaml');
|
||||
const result = getKeyInComponents(['components', 'schemas'], 'pet.yaml');
|
||||
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');
|
||||
});
|
||||
|
||||
it('should return ["components", "schemas", "pet.yaml", "definitions", "word"] when is pointing to a local ref',
|
||||
function () {
|
||||
const result = getKeyInComponents('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');
|
||||
});
|
||||
// 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", "folder/pet.yaml"] when there is an scaped slash', function () {
|
||||
const result = getKeyInComponents('schemas', 'folder~1pet.yaml');
|
||||
const result = getKeyInComponents(['components', 'schemas'], 'folder~1pet.yaml');
|
||||
expect(result.length).to.equal(3);
|
||||
expect(result[0]).to.equal('components');
|
||||
expect(result[1]).to.equal('schemas');
|
||||
@@ -37,41 +38,76 @@ describe('getKeyInComponents function', function () {
|
||||
|
||||
describe('getJsonPointerRelationToRoot function', function () {
|
||||
it('should return "#/components/schemas/Pets.yaml" no local path and schema', function () {
|
||||
let res = getJsonPointerRelationToRoot(jsonPointerEncodeAndReplace, 'Pets.yaml', 'Pets.yaml', 'schemas');
|
||||
let res = getJsonPointerRelationToRoot(
|
||||
jsonPointerEncodeAndReplace,
|
||||
'Pets.yaml',
|
||||
'Pets.yaml',
|
||||
['components', '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 () {
|
||||
let res = getJsonPointerRelationToRoot(jsonPointerEncodeAndReplace, 'hello.yaml', 'hello.yaml#/definitions/world',
|
||||
'schemas');
|
||||
let res = getJsonPointerRelationToRoot(
|
||||
jsonPointerEncodeAndReplace,
|
||||
'hello.yaml',
|
||||
'hello.yaml#/definitions/world',
|
||||
['components', 'schemas', 'hello.yaml']
|
||||
);
|
||||
expect(res).to.equal('#/components/schemas/hello.yaml/definitions/world');
|
||||
});
|
||||
it('should return "#/components/schemas/Error" no file path', function () {
|
||||
let res = getJsonPointerRelationToRoot(jsonPointerEncodeAndReplace, '', '#/components/schemas/Error', 'schemas');
|
||||
let res = getJsonPointerRelationToRoot(
|
||||
jsonPointerEncodeAndReplace,
|
||||
'',
|
||||
'#/components/schemas/Error',
|
||||
['components', 'schemas']
|
||||
);
|
||||
expect(res).to.equal('#/components/schemas/Error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('concatJsonPointer function ', function () {
|
||||
it('should return "#/components/schemas/Pets.yaml" no local path and schema', function () {
|
||||
let res = concatJsonPointer(jsonPointerEncodeAndReplace, 'Pets.yaml', 'schemas');
|
||||
let res = concatJsonPointer(
|
||||
jsonPointerEncodeAndReplace,
|
||||
'Pets.yaml',
|
||||
['components', 'schemas', 'Pets.yaml']
|
||||
);
|
||||
expect(res).to.equal('#/components/schemas/Pets.yaml');
|
||||
});
|
||||
|
||||
it('should return "#/components/schemas/other~1Pets.yaml" no local path and schema folder in filename', function () {
|
||||
let res = concatJsonPointer(jsonPointerEncodeAndReplace, 'other/Pets.yaml', 'schemas');
|
||||
let res = concatJsonPointer(
|
||||
jsonPointerEncodeAndReplace,
|
||||
'other/Pets.yaml',
|
||||
['components', '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', 'schemas');
|
||||
let res = concatJsonPointer(
|
||||
jsonPointerEncodeAndReplace,
|
||||
'some/Pet.yaml',
|
||||
['components', '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 () {
|
||||
let res = concatJsonPointer(jsonPointerEncodeAndReplace, 'hello.yaml', 'schemas', '/definitions/world');
|
||||
let res = concatJsonPointer(
|
||||
jsonPointerEncodeAndReplace,
|
||||
'hello.yaml',
|
||||
['components', 'schemas', 'hello.yaml'],
|
||||
'/definitions/world'
|
||||
);
|
||||
expect(res).to.equal('#/components/schemas/hello.yaml/definitions/world');
|
||||
});
|
||||
|
||||
it('should return "#/components/schemas/~1Pets.yaml" no local path and schema', function () {
|
||||
let res = concatJsonPointer(jsonPointerEncodeAndReplace, '/Pets.yaml', 'schemas');
|
||||
let res = concatJsonPointer(
|
||||
jsonPointerEncodeAndReplace,
|
||||
'/Pets.yaml',
|
||||
['components', 'schemas', '/Pets.yaml']
|
||||
);
|
||||
expect(res).to.equal('#/components/schemas/~1Pets.yaml');
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user