diff --git a/lib/open-api-mocker.js b/lib/open-api-mocker.js index a4541b3..48e0863 100644 --- a/lib/open-api-mocker.js +++ b/lib/open-api-mocker.js @@ -1,6 +1,6 @@ 'use strict'; -const jsonRefs = require('json-refs'); +const SwaggerParser = require('@apidevtools/swagger-parser'); const { Parser: OpenApiParser } = require('./openapi'); const { Parser: ServersParser } = require('./servers'); @@ -10,6 +10,7 @@ const OpenAPISchemaInvalid = require('./errors/openapi-schema-invalid-error'); const optionsBuilder = require('./utils/options-builder'); const ExplicitSchemaLoader = require('./schema-loaders/explicit-loader'); +const replaceCircularReferencesFromObject = require('./utils/replace-circular-references-from-object'); class OpenApiMocker { @@ -41,9 +42,7 @@ class OpenApiMocker { this.schema = await this.schema; try { - const parsedSchemas = await jsonRefs.resolveRefs(this.schema); - - this.schema = parsedSchemas.resolved; + this.schema = await SwaggerParser.validate(this.schema); const openApiParser = new OpenApiParser(); const openapi = openApiParser.parse(this.schema); @@ -51,6 +50,9 @@ class OpenApiMocker { const serversParser = new ServersParser(); const servers = serversParser.parse(this.schema); + const defaultValue = {'type' : 'string'} + this.schema = replaceCircularReferencesFromObject(this.schema, defaultValue, 3); + const pathsParser = new PathsParser(); const paths = pathsParser.parse(this.schema); diff --git a/lib/utils/replace-circular-references-from-object.js b/lib/utils/replace-circular-references-from-object.js new file mode 100644 index 0000000..bf665d1 --- /dev/null +++ b/lib/utils/replace-circular-references-from-object.js @@ -0,0 +1,78 @@ +/** + * Returns the given object without circular references. + * Replaces circular references with the given default value if the number of circular references exceeds the given depth limit. + * @param {Object} obj - The object to remove circular references from. + * @param {*} defaultValue - The default value to use for replacing circular references. + * @param {number} depthLimit - The maximum number of circular references allowed before using the default value. + * @returns {Object} - The object without circular references. + */ +function replaceCircularReferencesFromObject(obj, defaultValue, depthLimit) { + const circularReferenceReplacer = new CircularReferenceReplacer(defaultValue, depthLimit); + const objWithoutCircularReferences = circularReferenceReplacer.replace(obj); + return objWithoutCircularReferences; +} + +class CircularReferenceReplacer { + #depthLimit; + #defaultValue; + + /** + * @param {*} defaultValue - The default value to use for replacing circular references. + * @param {number} depthLimit - The maximum number of circular references allowed before using the default value. + */ + constructor(defaultValue, depthLimit) { + this.#depthLimit = depthLimit; + this.#defaultValue = defaultValue; + } + + replace(obj) { + return this.#replaceRecursively(obj, []); + } + + #replaceRecursively(obj, ancestors) { + if (!this.#isJSONObjectOrArray(obj)) { + return obj; + } + if (this.#hasCircularReferences(obj, ancestors) && this.#exceedsDepthLimit(obj, ancestors)) { + return this.#defaultValue; + } + ancestors.push(obj); // push the original reference + obj = Array.isArray(obj) ? [...obj] : { ...obj }; + for (let key in obj) { + obj[key] = this.#replaceRecursively( + obj[key], + [...ancestors] // copy of ancestors avoiding mutation by siblings + ); + } + return obj; + } + + #hasCircularReferences(obj, ancestors) { + const hasCircularReferences = ancestors.includes(obj); + return hasCircularReferences; + } + + #exceedsDepthLimit(obj, ancestors) { + const exceedsCircularReferencesLimit = this.#countOccurrences(obj, ancestors) >= this.#depthLimit; + return exceedsCircularReferencesLimit; + } + + #countOccurrences(value, array) { + const count = array.reduce((accumulator, currentValue) => (currentValue === value ? accumulator + 1 : accumulator), 0); + return count; + } + + #isJSONObjectOrArray(obj) { + return ( + typeof obj === 'object' && + obj !== null && + !(obj instanceof Boolean) && + !(obj instanceof Date) && + !(obj instanceof Number) && + !(obj instanceof RegExp) && + !(obj instanceof String) + ); + } +} + +module.exports = replaceCircularReferencesFromObject; diff --git a/package.json b/package.json index b85229f..78ae2b7 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "sinon": "^15.1.0" }, "dependencies": { + "@apidevtools/swagger-parser": "^10.1.0", "@faker-js/faker": "^8.0.2", "ajv": "^6.12.6", "ajv-openapi": "^2.0.0", @@ -48,7 +49,6 @@ "cors": "^2.8.5", "express": "^4.18.2", "js-yaml": "^4.1.0", - "json-refs": "^3.0.15", "lllog": "^1.1.2", "micro-memoize": "^4.1.2", "parse-prefer-header": "^1.0.0",