Resolving properties as a reusable component

Resolve each property as a different schema instead of inline
This commit is contained in:
Erik Mendoza
2022-06-07 21:39:53 -05:00
parent b9a01676de
commit 446e8651e5
30 changed files with 1141 additions and 195 deletions

View File

@@ -0,0 +1,72 @@
const COMPONENTS_KEYS_30 = [
'schemas',
'responses',
'parameters',
'examples',
'requestBodies',
'headers',
'securitySchemes',
'links',
'callbacks'
],
SCHEMA_CONTAINERS = [
'allOf',
'oneOf',
'anyOf',
'not',
'additionalProperties',
'items',
'schema'
],
EXAMPLE_CONTAINERS = [
'example'
],
PROPERTY_DEFINITION = [
'properties'
];
module.exports = {
/**
* Generates the trace to the key that will wrap de component using 3.0 version
* @param {array} traceFromParent - The trace from the parent key
* @param {string} filePathName - The filePath name from the file
* @param {string} localPart - The local path part
* @param {function} jsonPointerDecodeAndReplace - Function to decode a json pointer
* @returns {array} The trace to the container key
*/
getKeyInComponents30: function (traceFromParent, filePathName, localPart, jsonPointerDecodeAndReplace) {
let res = traceFromParent,
trace = [],
traceToKey = [],
matchFound = false,
isInComponents = traceFromParent[0] === 'components';
if (isInComponents) {
return [];
}
res.push(jsonPointerDecodeAndReplace(`${filePathName}${localPart}`));
trace = [...res].reverse();
for (let [index, item] of trace.entries()) {
if (SCHEMA_CONTAINERS.includes(item)) {
item = 'schemas';
}
if (EXAMPLE_CONTAINERS.includes(item)) {
item = 'examples';
}
if (PROPERTY_DEFINITION.includes(trace[index + 2])) {
trace[index + 1] = 'schemas';
}
traceToKey.push(item);
if (COMPONENTS_KEYS_30.includes(item)) {
matchFound = true;
break;
}
}
return matchFound ?
traceToKey.reverse() :
[];
},
COMPONENTS_KEYS_30
};

View File

@@ -6,10 +6,12 @@ const {
removeLocalReferenceFromPath,
localPointer,
jsonPointerLevelSeparator,
isLocalRef
isLocalRef,
jsonPointerDecodeAndReplace
} = require('./jsonPointer'),
traverseUtility = require('traverse'),
parse = require('./parse.js');
parse = require('./parse.js'),
{ ParseError } = require('./common/ParseError');
let path = require('path'),
pathBrowserify = require('path-browserify'),
@@ -40,6 +42,18 @@ function comparePaths(path1, path2) {
return path1 === path2;
}
/**
* 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 parseFileOrThrow(fileContent) {
const result = parse.getOasObject(fileContent);
if (result.result === false) {
throw new ParseError(result.reason);
}
return result;
}
/**
* Calculates the path relative to parent
@@ -136,7 +150,9 @@ function getContentFromTrace(content, partial) {
return content;
}
partial = partial[0] === jsonPointerLevelSeparator ? partial.substring(1) : partial;
const trace = partial.split(jsonPointerLevelSeparator);
const trace = partial.split(jsonPointerLevelSeparator).map((item) => {
return jsonPointerDecodeAndReplace(item);
});
let currentValue = content;
currentValue = deref._getEscaped(content, trace, undefined);
return currentValue;
@@ -196,8 +212,8 @@ function getTraceFromParentKeyInComponents(nodeContext, property) {
[key, ...parentKeys],
nodeTrace = getRootFileTrace(nodeParentsKey),
[file, local] = property.split(localPointer),
[keyTraceInComponents, inComponents] = getKeyInComponents(nodeTrace, file, local);
return [keyTraceInComponents, inComponents];
keyTraceInComponents = getKeyInComponents(nodeTrace, file, local);
return keyTraceInComponents;
}
/**
@@ -227,7 +243,7 @@ function getReferences (currentNode, isOutOfRoot, pathSolver, parentFilename) {
);
if (hasReferenceTypeKey) {
const tempRef = calculatePath(parentFilename, property.$ref),
[nodeTrace] = getTraceFromParentKeyInComponents(this, tempRef),
nodeTrace = getTraceFromParentKeyInComponents(this, tempRef),
referenceInDocument = getJsonPointerRelationToRoot(
jsonPointerEncodeAndReplace,
tempRef,
@@ -281,7 +297,7 @@ function getNodeContentAndReferences (currentNode, allData, specRoot) {
nodeContent = currentNode.parsed.oasObject;
}
else {
nodeContent = parse.getOasObject(currentNode.content).oasObject;
nodeContent = parseFileOrThrow(currentNode.content).oasObject;
}
const { referencesInNode, nodeReferenceDirectory } = getReferences(
@@ -321,6 +337,17 @@ function getNodeContentAndReferences (currentNode, allData, specRoot) {
* @returns {object} The components object related to the file
*/
function generateComponentsObject (documentContext, rootContent, refTypeResolver, components) {
let notInLine = Object.entries(documentContext.globalReferences).filter(([, value]) => {
return value.keyInComponents.length !== 0;
});
notInLine.forEach(([key, value]) => {
let [, partial] = key.split('#');
setValueInComponents(
value.keyInComponents,
components,
getContentFromTrace(documentContext.nodeContents[key], partial)
);
});
[rootContent, components].forEach((contentData) => {
traverseUtility(contentData).forEach(function (property) {
if (property) {
@@ -375,6 +402,22 @@ function generateComponentsObject (documentContext, rootContent, refTypeResolver
});
}
/**
* Generates the components object wrapper
* @param {object} parsedOasObject The parsed root
* @param {string} version - The current version
* @returns {object} The components object wrapper
*/
function generateComponentsWrapper(parsedOasObject) {
let components = {};
if (parsedOasObject.hasOwnProperty('components')) {
components = parsedOasObject.components;
}
return components;
}
module.exports = {
/**
* Takes in an spec root file and an array of data files
@@ -396,14 +439,18 @@ module.exports = {
rootContextData = algorithm.traverseAndBundle(specRoot, (currentNode) => {
return getNodeContentAndReferences(currentNode, allData, specRoot);
});
if (specRoot.parsed.oasObject.hasOwnProperty('components')) {
components = specRoot.parsed.oasObject.components;
}
generateComponentsObject(rootContextData, rootContextData.nodeContents[specRoot.fileName], isExtRef, components);
components = generateComponentsWrapper(specRoot.parsed.oasObject);
generateComponentsObject(
rootContextData,
rootContextData.nodeContents[specRoot.fileName],
isExtRef,
components
);
return {
fileContent: rootContextData.nodeContents[specRoot.fileName],
components
};
},
getReferences
getReferences,
parseFileOrThrow
};

View File

@@ -6,26 +6,7 @@ const slashes = /\//g,
escapedTilde = /~0/g,
jsonPointerLevelSeparator = '/',
escapedTildeString = '~0',
COMPONENTS_KEYS = [
'schemas',
'responses',
'parameters',
'examples',
'requestBodies',
'headers',
'securitySchemes',
'links',
'callbacks'
],
SCHEMA_PARENT_KEYS_IN_DOC = [
'allOf',
'oneOf',
'anyOf',
'not',
'additionalProperties',
'items',
'schema'
];
{ getKeyInComponents30 } = require('./30XUtils/componentsParentMatcher');
/**
* Encodes a filepath name so it can be a json pointer
@@ -52,49 +33,14 @@ function jsonPointerDecodeAndReplace(filePathName) {
* @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
* @param {string} version - The current spec version
* @returns {Array} - the calculated keys in an array representing each nesting property name
*/
function getKeyInComponents(traceFromParent, filePathName, localPath) {
const localPart = localPath ? `${localPointer}${localPath}` : '';
let res = traceFromParent,
trace = [],
traceToKey = [],
matchFound = false,
inComponents = false;
if (traceFromParent[0] === 'components') {
inComponents = true;
return [[], inComponents];
}
res.push(jsonPointerDecodeAndReplace(`${filePathName}${localPart}`));
trace = [...res].reverse();
for (let item of trace) {
if (SCHEMA_PARENT_KEYS_IN_DOC.includes(item)) {
item = 'schemas';
}
traceToKey.push(item);
if (COMPONENTS_KEYS.includes(item)) {
matchFound = true;
break;
}
}
return [matchFound ?
traceToKey.reverse() :
[], inComponents];
}
/**
* returns the local path of a pointer #/definitions/dog etc.
* @param {string} jsonPointer the complet pointer
* @returns {string} - the calculated key
*/
function getLocalPath(jsonPointer) {
if (jsonPointer.includes(localPointer)) {
return jsonPointer.split(localPointer)[1];
}
return '';
let result;
result = getKeyInComponents30(traceFromParent, filePathName, localPart, jsonPointerDecodeAndReplace);
return result;
}
/**
@@ -102,13 +48,14 @@ function getLocalPath(jsonPointer) {
* @constructor
* @param {Function} encodeFunction function to encode url
* @param {string} traceFromParent the trace from parent.
* @param {string} targetInRoot - The root element where we will point
* @returns {string} - the concatenated json pointer
*/
function concatJsonPointer(encodeFunction, traceFromParent) {
function concatJsonPointer(encodeFunction, traceFromParent, targetInRoot) {
const traceFromParentAsString = traceFromParent.map((trace) => {
return encodeFunction(trace);
}).join('/');
return localPointer + '/components' + jsonPointerLevelSeparator + traceFromParentAsString;
return localPointer + targetInRoot + jsonPointerLevelSeparator + traceFromParentAsString;
}
/**
@@ -117,14 +64,15 @@ function concatJsonPointer(encodeFunction, traceFromParent) {
* @param {Function} encodeFunction function to encode url
* @param {string} refValue the type of component e.g. schemas, parameters, etc.
* @param {string} traceFromKey the trace from the parent node.
* @param {string} version - The version we are working on
* @returns {string} - the concatenated json pointer
*/
function getJsonPointerRelationToRoot(encodeFunction, refValue, traceFromKey) {
let targetInRoot = '/components';
if (refValue.startsWith(localPointer)) {
return refValue;
}
const localPath = getLocalPath(refValue);
return concatJsonPointer(encodeFunction, traceFromKey, localPath);
return concatJsonPointer(encodeFunction, traceFromKey, targetInRoot);
}
/**

View File

@@ -92,7 +92,7 @@ const { formatDataPath, checkIsCorrectType, isKnownType } = require('./common/sc
{ getRelatedFiles } = require('./relatedFiles'),
{ compareVersion } = require('./common/versionUtils.js'),
parse = require('./parse'),
{ getBundleContentAndComponents } = require('./bundle.js'),
{ getBundleContentAndComponents, parseFileOrThrow } = require('./bundle.js'),
MULTI_FILE_API_TYPE_ALLOWED_VALUE = 'multiFile';
/* eslint-enable */
@@ -4884,7 +4884,7 @@ module.exports = {
mapProcessRelatedFiles(rootFiles, inputData, origin, version, format, toBundle = false) {
let bundleFormat = format,
parsedRootFiles = rootFiles.map((rootFile) => {
let parsedContent = this.parseFileOrThrow(rootFile.content);
let parsedContent = parseFileOrThrow(rootFile.content);
return { fileName: rootFile.fileName, content: rootFile.content, parsed: parsedContent };
}).filter((rootWithParsedContent) => {
bundleFormat = bundleFormat ? bundleFormat : rootWithParsedContent.parsed.inputFormat;
@@ -4968,13 +4968,5 @@ module.exports = {
throw new Error('"Path" of the data element should be provided');
}
},
parseFileOrThrow(fileContent) {
const result = parse.getOasObject(fileContent);
if (result.result === false) {
throw new ParseError(result.reason);
}
return result;
},
MULTI_FILE_API_TYPE_ALLOWED_VALUE
};