diff --git a/lib/30XUtils/componentsParentMatcher.js b/lib/30XUtils/componentsParentMatcher.js deleted file mode 100644 index 330e717..0000000 --- a/lib/30XUtils/componentsParentMatcher.js +++ /dev/null @@ -1,102 +0,0 @@ -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' - ], - RESPONSE_DEFINITION = [ - 'responses' - ], - REQUEST_BODY_CONTAINER = [ - 'requestBody' - ], - LINKS_CONTAINER = [ - 'links' - ], - HEADER_DEFINITION = [ - 'headers' - ], - CALLBACK_DEFINITION = [ - 'callbacks' - ]; - -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 (REQUEST_BODY_CONTAINER.includes(item)) { - item = 'requestBodies'; - } - if (LINKS_CONTAINER.includes(trace[index + 2])) { - trace[index + 1] = 'links'; - } - if (PROPERTY_DEFINITION.includes(trace[index + 2])) { - trace[index + 1] = 'schemas'; - } - traceToKey.push(item); - if (COMPONENTS_KEYS_30.includes(item)) { - matchFound = true; - break; - } - if (RESPONSE_DEFINITION.includes(trace[index + 2])) { - trace[index + 1] = 'responses'; - } - if (HEADER_DEFINITION.includes(trace[index + 2])) { - trace[index + 1] = 'headers'; - } - if (CALLBACK_DEFINITION.includes(trace[index + 2])) { - trace[index + 1] = 'callbacks'; - } - } - return matchFound ? - traceToKey.reverse() : - []; - }, - COMPONENTS_KEYS_30 -}; diff --git a/lib/31XUtils/componentsParentMatcher.js b/lib/31XUtils/componentsParentMatcher.js deleted file mode 100644 index 1dc097a..0000000 --- a/lib/31XUtils/componentsParentMatcher.js +++ /dev/null @@ -1,109 +0,0 @@ -const COMPONENTS_KEYS_31 = [ - 'schemas', - 'responses', - 'parameters', - 'examples', - 'requestBodies', - 'headers', - 'securitySchemes', - 'links', - 'callbacks', - 'pathItems' - ], - SCHEMA_CONTAINERS = [ - 'allOf', - 'oneOf', - 'anyOf', - 'not', - 'additionalProperties', - 'items', - 'schema' - ], - EXAMPLE_CONTAINERS = [ - 'example' - ], - PROPERTY_DEFINITION = [ - 'properties' - ], - RESPONSE_DEFINITION = [ - 'responses' - ], - REQUEST_BODY_CONTAINER = [ - 'requestBody' - ], - LINKS_CONTAINER = [ - 'links' - ], - HEADER_DEFINITION = [ - 'headers' - ], - CALLBACK_DEFINITION = [ - 'callbacks' - ], - PATH_ITEM_CONTAINER = [ - 'paths' - ]; - -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 - */ - getKeyInComponents31: 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 (REQUEST_BODY_CONTAINER.includes(item)) { - item = 'requestBodies'; - } - if (LINKS_CONTAINER.includes(trace[index + 2])) { - trace[index + 1] = 'links'; - } - if (PATH_ITEM_CONTAINER.includes(trace[index + 2])) { - trace[index + 1] = 'pathItems'; - } - if (PROPERTY_DEFINITION.includes(trace[index + 2])) { - trace[index + 1] = 'schemas'; - } - traceToKey.push(item); - if (COMPONENTS_KEYS_31.includes(item)) { - matchFound = true; - break; - } - if (RESPONSE_DEFINITION.includes(trace[index + 2])) { - trace[index + 1] = 'responses'; - } - if (HEADER_DEFINITION.includes(trace[index + 2])) { - trace[index + 1] = 'headers'; - } - if (CALLBACK_DEFINITION.includes(trace[index + 2])) { - trace[index + 1] = 'callbacks'; - } - } - return matchFound ? - traceToKey.reverse() : - []; - }, - COMPONENTS_KEYS_31 -}; diff --git a/lib/bundle.js b/lib/bundle.js index 1b5c09b..06f6bed 100644 --- a/lib/bundle.js +++ b/lib/bundle.js @@ -1,5 +1,4 @@ -const { COMPONENTS_KEYS_30 } = require('./30XUtils/componentsParentMatcher'), - { +const { isExtRef, getKeyInComponents, getJsonPointerRelationToRoot, @@ -11,8 +10,8 @@ const { COMPONENTS_KEYS_30 } = require('./30XUtils/componentsParentMatcher'), } = require('./jsonPointer'), traverseUtility = require('traverse'), parse = require('./parse.js'), - { COMPONENTS_KEYS_31 } = require('./31XUtils/componentsParentMatcher'), { ParseError } = require('./common/ParseError'); +const { getBundleRulesDataByVersion } = require('./common/versionUtils'); let path = require('path'), pathBrowserify = require('path-browserify'), @@ -156,7 +155,7 @@ function getContentFromTrace(content, partial) { * @returns {null} It modifies components global context */ function setValueInComponents(keyInComponents, components, value, version) { - const COMPONENTS_KEYS = version === '3.1' ? COMPONENTS_KEYS_31 : COMPONENTS_KEYS_30; + const { COMPONENTS_KEYS } = getBundleRulesDataByVersion(version); let currentPlace = components, target = keyInComponents[keyInComponents.length - 2], key = keyInComponents.length === 2 && COMPONENTS_KEYS.includes(keyInComponents[0]) ? diff --git a/lib/bundleRules/resolvers.js b/lib/bundleRules/resolvers.js new file mode 100644 index 0000000..8290e2e --- /dev/null +++ b/lib/bundleRules/resolvers.js @@ -0,0 +1,33 @@ +module.exports = { + /** + * Resolve the scenario if the item is in second level + * @param {array} trace The keyInComponents + * @param {number} index the current index + * @return {undefined} + */ + + resolveSecondLevelChild: function (trace, index, definitions) { + const item = definitions[trace[index + 2]]; + if (item) { + trace[index + 1] = item; + } + }, + + /** + * If the provided item is included in any defined container it returns the container name + * else it return the same item + * @param {string} item The current item in the iteration + * @returns {string} the name of container where item is included or the item if it's not included + * in any container + */ + + resolveFirstLevelChild: function(item, containers) { + for (let [key, containerItems] of Object.entries(containers)) { + if (containerItems.includes(item)) { + return key; + } + } + + return item; + } +}; diff --git a/lib/bundleRules/spec30.js b/lib/bundleRules/spec30.js new file mode 100644 index 0000000..bdce520 --- /dev/null +++ b/lib/bundleRules/spec30.js @@ -0,0 +1,52 @@ +const SCHEMA_CONTAINERS = [ + 'allOf', + 'oneOf', + 'anyOf', + 'not', + 'additionalProperties', + 'items', + 'schema' + ], + EXAMPLE_CONTAINERS = [ + 'example' + ], + REQUEST_BODY_CONTAINER = [ + 'requestBody' + ], + CONTAINERS = { + schemas: SCHEMA_CONTAINERS, + examples: EXAMPLE_CONTAINERS, + requestBodies: REQUEST_BODY_CONTAINER + }, + DEFINITIONS = { + headers: 'headers', + responses: 'responses', + callbacks: 'callbacks', + properties: 'schemas', + links: 'links' + }, + INLINE = [], + ROOT_CONTAINERS_KEYS = [ + 'components' + ], + COMPONENTS_KEYS = [ + 'schemas', + 'responses', + 'parameters', + 'examples', + 'requestBodies', + 'headers', + 'securitySchemes', + 'links', + 'callbacks' + ]; + +module.exports = { + RULES_30: { + CONTAINERS, + DEFINITIONS, + COMPONENTS_KEYS, + INLINE, + ROOT_CONTAINERS_KEYS + } +}; diff --git a/lib/bundleRules/spec31.js b/lib/bundleRules/spec31.js new file mode 100644 index 0000000..4026d29 --- /dev/null +++ b/lib/bundleRules/spec31.js @@ -0,0 +1,54 @@ +const SCHEMA_CONTAINERS = [ + 'allOf', + 'oneOf', + 'anyOf', + 'not', + 'additionalProperties', + 'items', + 'schema' + ], + EXAMPLE_CONTAINERS = [ + 'example' + ], + REQUEST_BODY_CONTAINER = [ + 'requestBody' + ], + CONTAINERS = { + schemas: SCHEMA_CONTAINERS, + examples: EXAMPLE_CONTAINERS, + requestBodies: REQUEST_BODY_CONTAINER + }, + DEFINITIONS = { + headers: 'headers', + responses: 'responses', + callbacks: 'callbacks', + properties: 'schemas', + links: 'links', + paths: 'pathItems' + }, + INLINE = [], + ROOT_CONTAINERS_KEYS = [ + 'components' + ], + COMPONENTS_KEYS = [ + 'schemas', + 'responses', + 'parameters', + 'examples', + 'requestBodies', + 'headers', + 'securitySchemes', + 'links', + 'callbacks', + 'pathItems' + ]; + +module.exports = { + RULES_31: { + CONTAINERS, + DEFINITIONS, + COMPONENTS_KEYS, + INLINE, + ROOT_CONTAINERS_KEYS + } +}; diff --git a/lib/common/versionUtils.js b/lib/common/versionUtils.js index dd54021..c4baa71 100644 --- a/lib/common/versionUtils.js +++ b/lib/common/versionUtils.js @@ -6,7 +6,9 @@ const VERSION_30 = { key: 'openapi', version: '3.0' }, DEFAULT_SPEC_VERSION = VERSION_30.version, SWAGGER_VERSION = VERSION_20.version, VERSION_3_1 = VERSION_31.version, - fs = require('fs'); + fs = require('fs'), + { RULES_30 } = require('./../bundleRules/spec30'), + { RULES_31 } = require('./../bundleRules/spec31'); /** * gets the version key and the version and generates a regular expression that @@ -268,6 +270,21 @@ function validateSupportedVersion(version) { return isValid !== undefined; } +/** + * Return the bundling rules to follow in the bundling process + * @param {string} version The spec version we are bundling + * @returns {object} The bundling rules related with the spec + */ +function getBundleRulesDataByVersion(version) { + const is31 = compareVersion(version, VERSION_31.version); + if (is31) { + return RULES_31; + } + else { + return RULES_30; + } +} + module.exports = { getSpecVersion, getConcreteSchemaUtils, @@ -277,5 +294,6 @@ module.exports = { getVersionRegexBySpecificationVersion, SWAGGER_VERSION, VERSION_3_1, - validateSupportedVersion + validateSupportedVersion, + getBundleRulesDataByVersion }; diff --git a/lib/jsonPointer.js b/lib/jsonPointer.js index ed18e28..83b31a0 100644 --- a/lib/jsonPointer.js +++ b/lib/jsonPointer.js @@ -6,9 +6,11 @@ const slashes = /\//g, escapedTilde = /~0/g, jsonPointerLevelSeparator = '/', escapedTildeString = '~0', - { getKeyInComponents30 } = require('./30XUtils/componentsParentMatcher'), - { getKeyInComponents31 } = require('./31XUtils/componentsParentMatcher'), - { VERSION_3_1 } = require('./common/versionUtils'); + { getBundleRulesDataByVersion } = require('./common/versionUtils'), + { + resolveFirstLevelChild, + resolveSecondLevelChild + } = require('./bundleRules/resolvers'); /** * Encodes a filepath name so it can be a json pointer @@ -51,15 +53,44 @@ function jsonPointerDecodeAndReplace(filePathName) { */ function getKeyInComponents(traceFromParent, filePathName, localPath, version) { const localPart = localPath ? `${localPointer}${localPath}` : '', - is31 = version === VERSION_3_1; - let result; + { + CONTAINERS, + DEFINITIONS, + COMPONENTS_KEYS, + INLINE, + ROOT_CONTAINERS_KEYS + } = getBundleRulesDataByVersion(version); + let result, + trace = [ + ...traceFromParent, + jsonPointerDecodeAndReplace(`${filePathName}${localPart}`) + ].reverse(), + traceToKey = [], + matchFound = false, + isRootAndReusableItemsContainer = ROOT_CONTAINERS_KEYS.includes(traceFromParent[0]); - if (is31) { - result = getKeyInComponents31(traceFromParent, filePathName, localPart, jsonPointerDecodeAndReplace); + if (isRootAndReusableItemsContainer) { + return []; } - else { - result = getKeyInComponents30(traceFromParent, filePathName, localPart, jsonPointerDecodeAndReplace); + + for (let [index, item] of trace.entries()) { + let itemShouldBeResolvedInline = INLINE.includes(item); + + if (itemShouldBeResolvedInline) { + matchFound = false; + break; + } + item = resolveFirstLevelChild(item, CONTAINERS); + resolveSecondLevelChild(trace, index, DEFINITIONS); + traceToKey.push(item); + if (COMPONENTS_KEYS.includes(item)) { + matchFound = true; + break; + } } + result = matchFound ? + traceToKey.reverse() : + []; return result.map(generateObjectName); }