UNPKG

openapi-resolver

Version:

Open API Resolver - Javascript library to connect to parse and resolve openapi-enabled APIs via browser or nodejs

1,643 lines (1,500 loc) 755 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["OpenApiResolver"] = factory(); else root["OpenApiResolver"] = factory(); })(this, () => { return /******/ (() => { // webpackBootstrap /******/ var __webpack_modules__ = ({ /***/ 8575: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; const Options = __webpack_require__(8316); module.exports = normalizeArgs; /** * Normalizes the given arguments, accounting for optional args. * * @param {Arguments} args * @returns {object} */ function normalizeArgs(args) { let path, schema, options, callback; args = Array.prototype.slice.call(args); if (typeof args[args.length - 1] === "function") { // The last parameter is a callback function callback = args.pop(); } if (typeof args[0] === "string") { // The first parameter is the path path = args[0]; if (typeof args[2] === "object") { // The second parameter is the schema, and the third parameter is the options schema = args[1]; options = args[2]; } else { // The second parameter is the options schema = undefined; options = args[1]; } } else { // The first parameter is the schema path = ""; schema = args[0]; options = args[1]; } if (!(options instanceof Options)) { options = new Options(options); } return { path, schema, options, callback }; } /***/ }), /***/ 8316: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; /* eslint lines-around-comment: [2, {beforeBlockComment: false}] */ const jsonParser = __webpack_require__(9324); const yamlParser = __webpack_require__(8015); const textParser = __webpack_require__(4844); const binaryParser = __webpack_require__(4486); const fileResolver = __webpack_require__(9571); const httpResolver = __webpack_require__(6796); module.exports = $RefParserOptions; /** * Options that determine how JSON schemas are parsed, resolved, and dereferenced. * * @param {object|$RefParserOptions} [options] - Overridden options * @constructor */ function $RefParserOptions(options) { merge(this, $RefParserOptions.defaults); merge(this, options); } $RefParserOptions.defaults = { /** * Determines how different types of files will be parsed. * * You can add additional parsers of your own, replace an existing one with * your own implementation, or disable any parser by setting it to false. */ parse: { json: jsonParser, yaml: yamlParser, text: textParser, binary: binaryParser }, /** * Determines how JSON References will be resolved. * * You can add additional resolvers of your own, replace an existing one with * your own implementation, or disable any resolver by setting it to false. */ resolve: { file: fileResolver, http: httpResolver, /** * Determines whether external $ref pointers will be resolved. * If this option is disabled, then none of above resolvers will be called. * Instead, external $ref pointers will simply be ignored. * * @type {boolean} */ external: true }, /** * By default, JSON Schema $Ref Parser throws the first error it encounters. Setting `continueOnError` to `true` * causes it to keep processing as much as possible and then throw a single error that contains all errors * that were encountered. */ continueOnError: false, /** * Determines the types of JSON references that are allowed. */ dereference: { /** * Dereference circular (recursive) JSON references? * If false, then a {@link ReferenceError} will be thrown if a circular reference is found. * If "ignore", then circular references will not be dereferenced. * * @type {boolean|string} */ circular: true, /** * A function, called for each path, which can return true to stop this path and all * subpaths from being dereferenced further. This is useful in schemas where some * subpaths contain literal $ref keys that should not be dereferenced. * * @type {function} */ excludedPathMatcher: () => false } }; /** * Merges the properties of the source object into the target object. * * @param {object} target - The object that we're populating * @param {?object} source - The options that are being merged * @returns {object} */ function merge(target, source) { if (isMergeable(source)) { let keys = Object.keys(source); for (let i = 0; i < keys.length; i++) { let key = keys[i]; let sourceSetting = source[key]; let targetSetting = target[key]; if (isMergeable(sourceSetting)) { // It's a nested object, so merge it recursively target[key] = merge(targetSetting || {}, sourceSetting); } else if (sourceSetting !== undefined) { // It's a scalar value, function, or array. No merging necessary. Just overwrite the target value. target[key] = sourceSetting; } } } return target; } /** * Determines whether the given value can be merged, * or if it is a scalar value that should just override the target value. * * @param {*} val * @returns {Boolean} */ function isMergeable(val) { return val && typeof val === "object" && !Array.isArray(val) && !(val instanceof RegExp) && !(val instanceof Date); } /***/ }), /***/ 3265: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; /* provided dependency */ var Buffer = __webpack_require__(5851)["Buffer"]; const { ono } = __webpack_require__(9978); const url = __webpack_require__(6930); const plugins = __webpack_require__(6250); const { ResolverError, ParserError, UnmatchedParserError, UnmatchedResolverError, isHandledError } = __webpack_require__(5554); module.exports = parse; /** * Reads and parses the specified file path or URL. * * @param {string} path - This path MUST already be resolved, since `read` doesn't know the resolution context * @param {$Refs} $refs * @param {$RefParserOptions} options * * @returns {Promise} * The promise resolves with the parsed file contents, NOT the raw (Buffer) contents. */ async function parse(path, $refs, options) { // Remove the URL fragment, if any path = url.stripHash(path); // Add a new $Ref for this file, even though we don't have the value yet. // This ensures that we don't simultaneously read & parse the same file multiple times let $ref = $refs._add(path); // This "file object" will be passed to all resolvers and parsers. let file = { url: path, extension: url.getExtension(path) }; // Read the file and then parse the data try { const resolver = await readFile(file, options, $refs); $ref.pathType = resolver.plugin.name; file.data = resolver.result; const parser = await parseFile(file, options, $refs); $ref.value = parser.result; return parser.result; } catch (err) { if (isHandledError(err)) { $ref.value = err; } throw err; } } /** * Reads the given file, using the configured resolver plugins * * @param {object} file - An object containing information about the referenced file * @param {string} file.url - The full URL of the referenced file * @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.) * @param {$RefParserOptions} options * * @returns {Promise} * The promise resolves with the raw file contents and the resolver that was used. */ function readFile(file, options, $refs) { return new Promise((resolve, reject) => { // console.log('Reading %s', file.url); // Find the resolvers that can read this file let resolvers = plugins.all(options.resolve); resolvers = plugins.filter(resolvers, "canRead", file); // Run the resolvers, in order, until one of them succeeds plugins.sort(resolvers); plugins.run(resolvers, "read", file, $refs).then(resolve, onError); function onError(err) { if (!err && options.continueOnError) { // No resolver could be matched reject(new UnmatchedResolverError(file.url)); } else if (!err || !("error" in err)) { // Throw a generic, friendly error. reject(ono.syntax(`Unable to resolve $ref pointer "${file.url}"`)); } // Throw the original error, if it's one of our own (user-friendly) errors. else if (err.error instanceof ResolverError) { reject(err.error); } else { reject(new ResolverError(err, file.url)); } } }); } /** * Parses the given file's contents, using the configured parser plugins. * * @param {object} file - An object containing information about the referenced file * @param {string} file.url - The full URL of the referenced file * @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.) * @param {*} file.data - The file contents. This will be whatever data type was returned by the resolver * @param {$RefParserOptions} options * * @returns {Promise} * The promise resolves with the parsed file contents and the parser that was used. */ function parseFile(file, options, $refs) { return new Promise((resolve, reject) => { // console.log('Parsing %s', file.url); // Find the parsers that can read this file type. // If none of the parsers are an exact match for this file, then we'll try ALL of them. // This handles situations where the file IS a supported type, just with an unknown extension. let allParsers = plugins.all(options.parse); let filteredParsers = plugins.filter(allParsers, "canParse", file); let parsers = filteredParsers.length > 0 ? filteredParsers : allParsers; // Run the parsers, in order, until one of them succeeds plugins.sort(parsers); plugins.run(parsers, "parse", file, $refs).then(onParsed, onError); function onParsed(parser) { if (!parser.plugin.allowEmpty && isEmpty(parser.result)) { reject(ono.syntax(`Error parsing "${file.url}" as ${parser.plugin.name}. \nParsed value is empty`)); } else { resolve(parser); } } function onError(err) { if (!err && options.continueOnError) { // No resolver could be matched reject(new UnmatchedParserError(file.url)); } else if (!err || !("error" in err)) { reject(ono.syntax(`Unable to parse ${file.url}`)); } else if (err.error instanceof ParserError) { reject(err.error); } else { reject(new ParserError(err.error.message, file.url)); } } }); } /** * Determines whether the parsed value is "empty". * * @param {*} value * @returns {boolean} */ function isEmpty(value) { return value === undefined || typeof value === "object" && Object.keys(value).length === 0 || typeof value === "string" && value.trim().length === 0 || Buffer.isBuffer(value) && value.length === 0; } /***/ }), /***/ 4486: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; /* provided dependency */ var Buffer = __webpack_require__(5851)["Buffer"]; let BINARY_REGEXP = /\.(jpeg|jpg|gif|png|bmp|ico)$/i; module.exports = { /** * The order that this parser will run, in relation to other parsers. * * @type {number} */ order: 400, /** * Whether to allow "empty" files (zero bytes). * * @type {boolean} */ allowEmpty: true, /** * Determines whether this parser can parse a given file reference. * Parsers that return true will be tried, in order, until one successfully parses the file. * Parsers that return false will be skipped, UNLESS all parsers returned false, in which case * every parser will be tried. * * @param {object} file - An object containing information about the referenced file * @param {string} file.url - The full URL of the referenced file * @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.) * @param {*} file.data - The file contents. This will be whatever data type was returned by the resolver * @returns {boolean} */ canParse(file) { // Use this parser if the file is a Buffer, and has a known binary extension return Buffer.isBuffer(file.data) && BINARY_REGEXP.test(file.url); }, /** * Parses the given data as a Buffer (byte array). * * @param {object} file - An object containing information about the referenced file * @param {string} file.url - The full URL of the referenced file * @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.) * @param {*} file.data - The file contents. This will be whatever data type was returned by the resolver * @returns {Buffer} */ parse(file) { if (Buffer.isBuffer(file.data)) { return file.data; } else { // This will reject if data is anything other than a string or typed array return Buffer.from(file.data); } } }; /***/ }), /***/ 9324: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; /* provided dependency */ var Buffer = __webpack_require__(5851)["Buffer"]; const { ParserError } = __webpack_require__(5554); module.exports = { /** * The order that this parser will run, in relation to other parsers. * * @type {number} */ order: 100, /** * Whether to allow "empty" files. This includes zero-byte files, as well as empty JSON objects. * * @type {boolean} */ allowEmpty: true, /** * Determines whether this parser can parse a given file reference. * Parsers that match will be tried, in order, until one successfully parses the file. * Parsers that don't match will be skipped, UNLESS none of the parsers match, in which case * every parser will be tried. * * @type {RegExp|string|string[]|function} */ canParse: ".json", /** * Parses the given file as JSON * * @param {object} file - An object containing information about the referenced file * @param {string} file.url - The full URL of the referenced file * @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.) * @param {*} file.data - The file contents. This will be whatever data type was returned by the resolver * @returns {Promise} */ async parse(file) { // eslint-disable-line require-await let data = file.data; if (Buffer.isBuffer(data)) { data = data.toString(); } if (typeof data === "string") { if (data.trim().length === 0) { return; // This mirrors the YAML behavior } else { try { return JSON.parse(data); } catch (e) { throw new ParserError(e.message, file.url); } } } else { // data is already a JavaScript value (object, array, number, null, NaN, etc.) return data; } } }; /***/ }), /***/ 4844: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; /* provided dependency */ var Buffer = __webpack_require__(5851)["Buffer"]; const { ParserError } = __webpack_require__(5554); let TEXT_REGEXP = /\.(txt|htm|html|md|xml|js|min|map|css|scss|less|svg)$/i; module.exports = { /** * The order that this parser will run, in relation to other parsers. * * @type {number} */ order: 300, /** * Whether to allow "empty" files (zero bytes). * * @type {boolean} */ allowEmpty: true, /** * The encoding that the text is expected to be in. * * @type {string} */ encoding: "utf8", /** * Determines whether this parser can parse a given file reference. * Parsers that return true will be tried, in order, until one successfully parses the file. * Parsers that return false will be skipped, UNLESS all parsers returned false, in which case * every parser will be tried. * * @param {object} file - An object containing information about the referenced file * @param {string} file.url - The full URL of the referenced file * @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.) * @param {*} file.data - The file contents. This will be whatever data type was returned by the resolver * @returns {boolean} */ canParse(file) { // Use this parser if the file is a string or Buffer, and has a known text-based extension return (typeof file.data === "string" || Buffer.isBuffer(file.data)) && TEXT_REGEXP.test(file.url); }, /** * Parses the given file as text * * @param {object} file - An object containing information about the referenced file * @param {string} file.url - The full URL of the referenced file * @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.) * @param {*} file.data - The file contents. This will be whatever data type was returned by the resolver * @returns {string} */ parse(file) { if (typeof file.data === "string") { return file.data; } else if (Buffer.isBuffer(file.data)) { return file.data.toString(this.encoding); } else { throw new ParserError("data is not text", file.url); } } }; /***/ }), /***/ 8015: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; /* provided dependency */ var Buffer = __webpack_require__(5851)["Buffer"]; const { ParserError } = __webpack_require__(5554); const yaml = __webpack_require__(7949); const { JSON_SCHEMA } = __webpack_require__(7949); module.exports = { /** * The order that this parser will run, in relation to other parsers. * * @type {number} */ order: 200, /** * Whether to allow "empty" files. This includes zero-byte files, as well as empty JSON objects. * * @type {boolean} */ allowEmpty: true, /** * Determines whether this parser can parse a given file reference. * Parsers that match will be tried, in order, until one successfully parses the file. * Parsers that don't match will be skipped, UNLESS none of the parsers match, in which case * every parser will be tried. * * @type {RegExp|string[]|function} */ canParse: [".yaml", ".yml", ".json"], // JSON is valid YAML /** * Parses the given file as YAML * * @param {object} file - An object containing information about the referenced file * @param {string} file.url - The full URL of the referenced file * @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.) * @param {*} file.data - The file contents. This will be whatever data type was returned by the resolver * @returns {Promise} */ async parse(file) { // eslint-disable-line require-await let data = file.data; if (Buffer.isBuffer(data)) { data = data.toString(); } if (typeof data === "string") { try { return yaml.load(data, { schema: JSON_SCHEMA }); } catch (e) { throw new ParserError(e.message, file.url); } } else { // data is already a JavaScript value (object, array, number, null, NaN, etc.) return data; } } }; /***/ }), /***/ 5860: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; module.exports = Pointer; const $Ref = __webpack_require__(6654); const url = __webpack_require__(6930); const { JSONParserError, InvalidPointerError, MissingPointerError, isHandledError } = __webpack_require__(5554); const slashes = /\//g; const tildes = /~/g; const escapedSlash = /~1/g; const escapedTilde = /~0/g; /** * This class represents a single JSON pointer and its resolved value. * * @param {$Ref} $ref * @param {string} path * @param {string} [friendlyPath] - The original user-specified path (used for error messages) * @constructor */ function Pointer($ref, path, friendlyPath) { /** * The {@link $Ref} object that contains this {@link Pointer} object. * @type {$Ref} */ this.$ref = $ref; /** * The file path or URL, containing the JSON pointer in the hash. * This path is relative to the path of the main JSON schema file. * @type {string} */ this.path = path; /** * The original path or URL, used for error messages. * @type {string} */ this.originalPath = friendlyPath || path; /** * The value of the JSON pointer. * Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays). * @type {?*} */ this.value = undefined; /** * Indicates whether the pointer references itself. * @type {boolean} */ this.circular = false; /** * The number of indirect references that were traversed to resolve the value. * Resolving a single pointer may require resolving multiple $Refs. * @type {number} */ this.indirections = 0; } /** * Resolves the value of a nested property within the given object. * * @param {*} obj - The object that will be crawled * @param {$RefParserOptions} options * @param {string} pathFromRoot - the path of place that initiated resolving * * @returns {Pointer} * Returns a JSON pointer whose {@link Pointer#value} is the resolved value. * If resolving this value required resolving other JSON references, then * the {@link Pointer#$ref} and {@link Pointer#path} will reflect the resolution path * of the resolved value. */ Pointer.prototype.resolve = function (obj, options, pathFromRoot) { let tokens = Pointer.parse(this.path, this.originalPath); // Crawl the object, one token at a time this.value = unwrapOrThrow(obj); for (let i = 0; i < tokens.length; i++) { if (resolveIf$Ref(this, options)) { // The $ref path has changed, so append the remaining tokens to the path this.path = Pointer.join(this.path, tokens.slice(i)); } if (typeof this.value === "object" && this.value !== null && "$ref" in this.value) { return this; } let token = tokens[i]; if (this.value[token] === undefined || this.value[token] === null) { this.value = null; throw new MissingPointerError(token, decodeURI(this.originalPath)); } else { this.value = this.value[token]; } } // Resolve the final value if (!this.value || this.value.$ref && url.resolve(this.path, this.value.$ref) !== pathFromRoot) { resolveIf$Ref(this, options); } return this; }; /** * Sets the value of a nested property within the given object. * * @param {*} obj - The object that will be crawled * @param {*} value - the value to assign * @param {$RefParserOptions} options * * @returns {*} * Returns the modified object, or an entirely new object if the entire object is overwritten. */ Pointer.prototype.set = function (obj, value, options) { let tokens = Pointer.parse(this.path); let token; if (tokens.length === 0) { // There are no tokens, replace the entire object with the new value this.value = value; return value; } // Crawl the object, one token at a time this.value = unwrapOrThrow(obj); for (let i = 0; i < tokens.length - 1; i++) { resolveIf$Ref(this, options); token = tokens[i]; if (this.value && this.value[token] !== undefined) { // The token exists this.value = this.value[token]; } else { // The token doesn't exist, so create it this.value = setValue(this, token, {}); } } // Set the value of the final token resolveIf$Ref(this, options); token = tokens[tokens.length - 1]; setValue(this, token, value); // Return the updated object return obj; }; /** * Parses a JSON pointer (or a path containing a JSON pointer in the hash) * and returns an array of the pointer's tokens. * (e.g. "schema.json#/definitions/person/name" => ["definitions", "person", "name"]) * * The pointer is parsed according to RFC 6901 * {@link https://tools.ietf.org/html/rfc6901#section-3} * * @param {string} path * @param {string} [originalPath] * @returns {string[]} */ Pointer.parse = function (path, originalPath) { // Get the JSON pointer from the path's hash let pointer = url.getHash(path).substr(1); // If there's no pointer, then there are no tokens, // so return an empty array if (!pointer) { return []; } // Split into an array pointer = pointer.split("/"); // Decode each part, according to RFC 6901 for (let i = 0; i < pointer.length; i++) { pointer[i] = decodeURIComponent(pointer[i].replace(escapedSlash, "/").replace(escapedTilde, "~")); } if (pointer[0] !== "") { throw new InvalidPointerError(pointer, originalPath === undefined ? path : originalPath); } return pointer.slice(1); }; /** * Creates a JSON pointer path, by joining one or more tokens to a base path. * * @param {string} base - The base path (e.g. "schema.json#/definitions/person") * @param {string|string[]} tokens - The token(s) to append (e.g. ["name", "first"]) * @returns {string} */ Pointer.join = function (base, tokens) { // Ensure that the base path contains a hash if (base.indexOf("#") === -1) { base += "#"; } // Append each token to the base path tokens = Array.isArray(tokens) ? tokens : [tokens]; for (let i = 0; i < tokens.length; i++) { let token = tokens[i]; // Encode the token, according to RFC 6901 base += "/" + encodeURIComponent(token.replace(tildes, "~0").replace(slashes, "~1")); } return base; }; /** * If the given pointer's {@link Pointer#value} is a JSON reference, * then the reference is resolved and {@link Pointer#value} is replaced with the resolved value. * In addition, {@link Pointer#path} and {@link Pointer#$ref} are updated to reflect the * resolution path of the new value. * * @param {Pointer} pointer * @param {$RefParserOptions} options * @returns {boolean} - Returns `true` if the resolution path changed */ function resolveIf$Ref(pointer, options) { // Is the value a JSON reference? (and allowed?) if ($Ref.isAllowed$Ref(pointer.value, options)) { let $refPath = url.resolve(pointer.path, pointer.value.$ref); if ($refPath === pointer.path) { // The value is a reference to itself, so there's nothing to do. pointer.circular = true; } else { let resolved = pointer.$ref.$refs._resolve($refPath, pointer.path, options); if (resolved === null) { return false; } pointer.indirections += resolved.indirections + 1; if ($Ref.isExtended$Ref(pointer.value)) { // This JSON reference "extends" the resolved value, rather than simply pointing to it. // So the resolved path does NOT change. Just the value does. pointer.value = $Ref.dereference(pointer.value, resolved.value); return false; } else { // Resolve the reference pointer.$ref = resolved.$ref; pointer.path = resolved.path; pointer.value = resolved.value; } return true; } } } /** * Sets the specified token value of the {@link Pointer#value}. * * The token is evaluated according to RFC 6901. * {@link https://tools.ietf.org/html/rfc6901#section-4} * * @param {Pointer} pointer - The JSON Pointer whose value will be modified * @param {string} token - A JSON Pointer token that indicates how to modify `obj` * @param {*} value - The value to assign * @returns {*} - Returns the assigned value */ function setValue(pointer, token, value) { if (pointer.value && typeof pointer.value === "object") { if (token === "-" && Array.isArray(pointer.value)) { pointer.value.push(value); } else { pointer.value[token] = value; } } else { throw new JSONParserError(`Error assigning $ref pointer "${pointer.path}". \nCannot set "${token}" of a non-object.`); } return value; } function unwrapOrThrow(value) { if (isHandledError(value)) { throw value; } return value; } /***/ }), /***/ 6654: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; module.exports = $Ref; const Pointer = __webpack_require__(5860); const { InvalidPointerError, isHandledError, normalizeError } = __webpack_require__(5554); const { safePointerToPath, stripHash, getHash } = __webpack_require__(6930); /** * This class represents a single JSON reference and its resolved value. * * @class */ function $Ref() { /** * The file path or URL of the referenced file. * This path is relative to the path of the main JSON schema file. * * This path does NOT contain document fragments (JSON pointers). It always references an ENTIRE file. * Use methods such as {@link $Ref#get}, {@link $Ref#resolve}, and {@link $Ref#exists} to get * specific JSON pointers within the file. * * @type {string} */ this.path = undefined; /** * The resolved value of the JSON reference. * Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays). * * @type {?*} */ this.value = undefined; /** * The {@link $Refs} object that contains this {@link $Ref} object. * * @type {$Refs} */ this.$refs = undefined; /** * Indicates the type of {@link $Ref#path} (e.g. "file", "http", etc.) * * @type {?string} */ this.pathType = undefined; /** * List of all errors. Undefined if no errors. * * @type {Array<JSONParserError | ResolverError | ParserError | MissingPointerError>} */ this.errors = undefined; } /** * Pushes an error to errors array. * * @param {Array<JSONParserError | JSONParserErrorGroup>} err - The error to be pushed * @returns {void} */ $Ref.prototype.addError = function (err) { if (this.errors === undefined) { this.errors = []; } const existingErrors = this.errors.map(({ footprint }) => footprint); // the path has been almost certainly set at this point, // but just in case something went wrong, normalizeError injects path if necessary // moreover, certain errors might point at the same spot, so filter them out to reduce noise if (Array.isArray(err.errors)) { this.errors.push(...err.errors.map(normalizeError).filter(({ footprint }) => !existingErrors.includes(footprint))); } else if (!existingErrors.includes(err.footprint)) { this.errors.push(normalizeError(err)); } }; /** * Determines whether the given JSON reference exists within this {@link $Ref#value}. * * @param {string} path - The full path being resolved, optionally with a JSON pointer in the hash * @param {$RefParserOptions} options * @returns {boolean} */ $Ref.prototype.exists = function (path, options) { try { this.resolve(path, options); return true; } catch (e) { return false; } }; /** * Resolves the given JSON reference within this {@link $Ref#value} and returns the resolved value. * * @param {string} path - The full path being resolved, optionally with a JSON pointer in the hash * @param {$RefParserOptions} options * @returns {*} - Returns the resolved value */ $Ref.prototype.get = function (path, options) { return this.resolve(path, options).value; }; /** * Resolves the given JSON reference within this {@link $Ref#value}. * * @param {string} path - The full path being resolved, optionally with a JSON pointer in the hash * @param {$RefParserOptions} options * @param {string} friendlyPath - The original user-specified path (used for error messages) * @param {string} pathFromRoot - The path of `obj` from the schema root * @returns {Pointer | null} */ $Ref.prototype.resolve = function (path, options, friendlyPath, pathFromRoot) { let pointer = new Pointer(this, path, friendlyPath); try { return pointer.resolve(this.value, options, pathFromRoot); } catch (err) { if (!options || !options.continueOnError || !isHandledError(err)) { throw err; } if (err.path === null) { err.path = safePointerToPath(getHash(pathFromRoot)); } if (err instanceof InvalidPointerError) { // this is a special case - InvalidPointerError is thrown when dereferencing external file, // but the issue is caused by the source file that referenced the file that undergoes dereferencing err.source = decodeURI(stripHash(pathFromRoot)); } this.addError(err); return null; } }; /** * Sets the value of a nested property within this {@link $Ref#value}. * If the property, or any of its parents don't exist, they will be created. * * @param {string} path - The full path of the property to set, optionally with a JSON pointer in the hash * @param {*} value - The value to assign */ $Ref.prototype.set = function (path, value) { let pointer = new Pointer(this, path); this.value = pointer.set(this.value, value); }; /** * Determines whether the given value is a JSON reference. * * @param {*} value - The value to inspect * @returns {boolean} */ $Ref.is$Ref = function (value) { return value && typeof value === "object" && typeof value.$ref === "string" && value.$ref.length > 0; }; /** * Determines whether the given value is an external JSON reference. * * @param {*} value - The value to inspect * @returns {boolean} */ $Ref.isExternal$Ref = function (value) { return $Ref.is$Ref(value) && value.$ref[0] !== "#"; }; /** * Determines whether the given value is a JSON reference, and whether it is allowed by the options. * For example, if it references an external file, then options.resolve.external must be true. * * @param {*} value - The value to inspect * @param {$RefParserOptions} options * @returns {boolean} */ $Ref.isAllowed$Ref = function (value, options) { if ($Ref.is$Ref(value)) { if (value.$ref.substr(0, 2) === "#/" || value.$ref === "#") { // It's a JSON Pointer reference, which is always allowed return true; } else if (value.$ref[0] !== "#" && (!options || options.resolve.external)) { // It's an external reference, which is allowed by the options return true; } } }; /** * Determines whether the given value is a JSON reference that "extends" its resolved value. * That is, it has extra properties (in addition to "$ref"), so rather than simply pointing to * an existing value, this $ref actually creates a NEW value that is a shallow copy of the resolved * value, plus the extra properties. * * @example: * { * person: { * properties: { * firstName: { type: string } * lastName: { type: string } * } * } * employee: { * properties: { * $ref: #/person/properties * salary: { type: number } * } * } * } * * In this example, "employee" is an extended $ref, since it extends "person" with an additional * property (salary). The result is a NEW value that looks like this: * * { * properties: { * firstName: { type: string } * lastName: { type: string } * salary: { type: number } * } * } * * @param {*} value - The value to inspect * @returns {boolean} */ $Ref.isExtended$Ref = function (value) { return $Ref.is$Ref(value) && Object.keys(value).length > 1; }; /** * Returns the resolved value of a JSON Reference. * If necessary, the resolved value is merged with the JSON Reference to create a new object * * @example: * { * person: { * properties: { * firstName: { type: string } * lastName: { type: string } * } * } * employee: { * properties: { * $ref: #/person/properties * salary: { type: number } * } * } * } * * When "person" and "employee" are merged, you end up with the following object: * * { * properties: { * firstName: { type: string } * lastName: { type: string } * salary: { type: number } * } * } * * @param {object} $ref - The JSON reference object (the one with the "$ref" property) * @param {*} resolvedValue - The resolved value, which can be any type * @returns {*} - Returns the dereferenced value */ $Ref.dereference = function ($ref, resolvedValue) { if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) { let merged = {}; for (let key of Object.keys($ref)) { if (key !== "$ref") { merged[key] = $ref[key]; } } for (let key of Object.keys(resolvedValue)) { if (!(key in merged)) { merged[key] = resolvedValue[key]; } } return merged; } else { // Completely replace the original reference with the resolved value return resolvedValue; } }; /***/ }), /***/ 8031: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; const { ono } = __webpack_require__(9978); const $Ref = __webpack_require__(6654); const url = __webpack_require__(6930); module.exports = $Refs; /** * This class is a map of JSON references and their resolved values. */ function $Refs() { /** * Indicates whether the schema contains any circular references. * * @type {boolean} */ this.circular = false; /** * A map of paths/urls to {@link $Ref} objects * * @type {object} * @protected */ this._$refs = {}; /** * The {@link $Ref} object that is the root of the JSON schema. * * @type {$Ref} * @protected */ this._root$Ref = null; } /** * Returns the paths of all the files/URLs that are referenced by the JSON schema, * including the schema itself. * * @param {...string|string[]} [types] - Only return paths of the given types ("file", "http", etc.) * @returns {string[]} */ $Refs.prototype.paths = function (types) { // eslint-disable-line no-unused-vars let paths = getPaths(this._$refs, arguments); return paths.map(path => { return path.decoded; }); }; /** * Returns the map of JSON references and their resolved values. * * @param {...string|string[]} [types] - Only return references of the given types ("file", "http", etc.) * @returns {object} */ $Refs.prototype.values = function (types) { // eslint-disable-line no-unused-vars let $refs = this._$refs; let paths = getPaths($refs, arguments); return paths.reduce((obj, path) => { obj[path.decoded] = $refs[path.encoded].value; return obj; }, {}); }; /** * Returns a POJO (plain old JavaScript object) for serialization as JSON. * * @returns {object} */ $Refs.prototype.toJSON = $Refs.prototype.values; /** * Determines whether the given JSON reference exists. * * @param {string} path - The path being resolved, optionally with a JSON pointer in the hash * @param {$RefParserOptions} [options] * @returns {boolean} */ $Refs.prototype.exists = function (path, options) { try { this._resolve(path, "", options); return true; } catch (e) { return false; } }; /** * Resolves the given JSON reference and returns the resolved value. * * @param {string} path - The path being resolved, with a JSON pointer in the hash * @param {$RefParserOptions} [options] * @returns {*} - Returns the resolved value */ $Refs.prototype.get = function (path, options) { return this._resolve(path, "", options).value; }; /** * Sets the value of a nested property within this {@link $Ref#value}. * If the property, or any of its parents don't exist, they will be created. * * @param {string} path - The path of the property to set, optionally with a JSON pointer in the hash * @param {*} value - The value to assign */ $Refs.prototype.set = function (path, value) { let absPath = url.resolve(this._root$Ref.path, path); let withoutHash = url.stripHash(absPath); let $ref = this._$refs[withoutHash]; if (!$ref) { throw ono(`Error resolving $ref pointer "${path}". \n"${withoutHash}" not found.`); } $ref.set(absPath, value); }; /** * Creates a new {@link $Ref} object and adds it to this {@link $Refs} object. * * @param {string} path - The file path or URL of the referenced file */ $Refs.prototype._add = function (path) { let withoutHash = url.stripHash(path); let $ref = new $Ref(); $ref.path = withoutHash; $ref.$refs = this; this._$refs[withoutHash] = $ref; this._root$Ref = this._root$Ref || $ref; return $ref; }; /** * Resolves the given JSON reference. * * @param {string} path - The path being resolved, optionally with a JSON pointer in the hash * @param {string} pathFromRoot - The path of `obj` from the schema root * @param {$RefParserOptions} [options] * @returns {Pointer} * @protected */ $Refs.prototype._resolve = function (path, pathFromRoot, options) { let absPath = url.resolve(this._root$Ref.path, path); let withoutHash = url.stripHash(absPath); let $ref = this._$refs[withoutHash]; if (!$ref) { throw ono(`Error resolving $ref pointer "${path}". \n"${withoutHash}" not found.`); } return $ref.resolve(absPath, options, path, pathFromRoot); }; /** * Returns the specified {@link $Ref} object, or undefined. * * @param {string} path - The path being resolved, optionally with a JSON pointer in the hash * @returns {$Ref|undefined} * @protected */ $Refs.prototype._get$Ref = function (path) { path = url.resolve(this._root$Ref.path, path); let withoutHash = url.stripHash(path); return this._$refs[withoutHash]; }; /** * Returns the encoded and decoded paths keys of the given object. * * @param {object} $refs - The object whose keys are URL-encoded paths * @param {...string|string[]} [types] - Only return paths of the given types ("file", "http", etc.) * @returns {object[]} */ function getPaths($refs, types) { let paths = Object.keys($refs); // Filter the paths by type types = Array.isArray(types[0]) ? types[0] : Array.prototype.slice.call(types); if (types.length > 0 && types[0]) { paths = paths.filter(key => { return types.indexOf($refs[key].pathType) !== -1; }); } // Decode local filesystem paths return paths.map(path => { return { encoded: path, decoded: $refs[path].pathType === "file" ? url.toFileSystemPath(path, true) : path }; }); } /***/ }), /***/ 3747: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; const $Ref = __webpack_require__(6654); const Pointer = __webpack_require__(5860); const parse = __webpack_require__(3265); const url = __webpack_require__(6930); const { isHandledError } = __webpack_require__(5554); module.exports = resolveExternal; /** * Crawls the JSON schema, finds all external JSON references, and resolves their values. * This method does not mutate the JSON schema. The resolved values are added to {@link $RefParser#$refs}. * * NOTE: We only care about EXTERNAL references here. INTERNAL references are only relevant when dereferencing. * * @param {$RefParser} parser * @param {$RefParserOptions} options * * @returns {Promise} * The promise resolves once all JSON references in the schema have been resolved, * including nested references that are contained in externally-referenced files. */ function resolveExternal(parser, options) { if (!options.resolve.external) { // Nothing to resolve, so exit early return Promise.resolve(); } try { // console.log('Resolving $ref pointers in %s', parser.$refs._root$Ref.path); let promises = crawl(parser.schema, parser.$refs._root$Ref.path + "#", parser.$refs, options); return Promise.all(promises); } catch (e) { return Promise.reject(e); } } /** * Recursively crawls the given value, and resolves any external JSON references. * * @param {*} obj - The value to crawl. If it's not an object or array, it will be ignored. * @param {string} path - The full path of `obj`, possibly with a JSON Pointer in the hash * @param {$Refs} $refs * @param {$RefParserOptions} options * @param {Set} seen - Internal. * * @returns {Promise[]} * Returns an array of promises. There will be one promise for each JSON reference in `obj`. * If `obj` does not contain any JSON references, then the array will be empty. * If any of the JSON references point to files that contain additional JSON references, * then the corresponding promise will internally reference an array of promises. */ function crawl(obj, path, $refs, options, seen) { seen = seen || new Set(); let promises = []; if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !seen.has(obj)) { seen.add(obj); // Track previously seen objects to avoid infinite recursion if ($Ref.isExternal$Ref(obj)) { promises.push(resolve$Ref(obj, path, $refs, options)); } else { for (let key of Object.keys(obj)) { let keyPath = Pointer.join(path, key); let value = obj[key]; if ($Ref.isExternal$Ref(value)) { promises.push(resolve$Ref(value, keyPath, $refs, options)); } else { promises = promises.concat(crawl(value, keyPath, $refs, options, seen)); } } } } return promises; } /** * Resolves the given JSON Reference, and then crawls the resulting value. * * @param {{$ref: string}} $ref - The JSON Reference to resolve * @param {string} path - The full path of `$ref`, possibly with a JSON Pointer in the hash * @param {$Refs} $refs * @param {$RefParserOptions} options * * @returns {Promise} * The promise resolves once all JSON references in the object have been resolved, * including nested references that are contained in externally-referenced files. */ async function resolve$Ref($ref, path, $refs, options) { // console.log('Resolving $ref pointer "%s" at %s', $ref.$ref, path); let resolvedPath = url.resolve(path, $ref.$ref); let withoutHash = url.stripHash(resolvedPath); // Do we already have this $ref? $ref = $refs._$refs[withoutHash]; if ($ref) { // We've already parsed this $ref, so use the existing value return Promise.resolve($ref.value); } // Parse the $referenced file/url try { const result = await parse(resolvedPath, $refs, options); // Crawl the parsed value // console.log('Resolving $ref pointers in %s', withoutHash); let promises = crawl(result, withoutHash + "#", $refs, options); return Promise.all(promises); } catch (err) { if (!options.continueOnError || !isHandledError(err)) { throw err; } if ($refs._$refs[withoutHash]) { err.source = decodeURI(url.stripHash(path)); err.path = url.safePointerToPath(url.getHash(path)); } return []; } } /***/ }), /***/ 9571: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; const fs = __webpack_require__(3471); const { ono } = __webpack_require__(9978); const url = __webpack_require__(6930); const { ResolverError } = __webpack_require__(5554); module.exports = { /** * The order that this resolver will run, in relation to other resolvers. * * @type {number} */ order: 100, /** * Determines whether this resolver can read a given file reference. * Resolvers that return true will be tried, in order, until one successfully resolves the file. * Resolvers that return false will not be given a chance to resolve the file. * * @param {object} file - An object containing information about the referenced file * @param {string} file.url - The full URL of the referenced file * @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.) * @returns {boolean} */ canRead(file) { return url.isFileSystemPath(file.url); }, /** * Reads the given file and returns its raw contents as a Buffer. * * @param {object} file - An object containing information about the referenced file * @param {string} file.url - The full URL of the referenced file * @param {string} file.extension - The lowercased file extension (e.g. ".txt", ".html", etc.) * @returns {Promise<Buffer>} */ read(file) { return new Promise((resolve, reject) => { let path; try { path = url.toFileSystemPath(file.url); } catch (err) { reject(new ResolverError(ono.uri(err, `Malformed URI: ${file.url}`), file.url)); } // console.log('Opening file: %s', path); try { fs.readFile(path, (err, data) => { if (err) { reject(new ResolverError(ono(err, `Error opening file "${path}"`), path)); } else { resolve(data); } }); } catch (err) { reject(new ResolverError(ono(err, `Error opening file "${path}"`), path)); } }); } }; /***/ }), /***/ 6796: /***/ ((module, __unused_webpack_exports, __webpack_require__) => { "use strict"; /* provided dependency */ var process = __webpack_require__(2790); /* provided dependency */ var Buffer = __webpack_require__(5851)["Buffer"]; const http = __webpack_require__(5262); const https = __webpack_require__(9804); const { ono } = __webpack_require__(9978); const url = __webpack_require__(6930); const { ResolverError } = __webpack_require__(5554); module.exports = { /** * The order that this resolver will run, in relation to other resolvers. * * @type {number} */ order: 200, /** * HTTP headers to send when downloading files. * * @example: * { * "User-Agent": "JSON Schema $Ref Parser", * Accept: "application/json" * } * * @type {object} */ headers: null, /** * HTTP request timeout (in milliseconds). * * @type {number} */ timeout: 5000, // 5 seconds /** * The maximum number of HTTP redirects to follow. * To disable automatic following of redirects, set this to zero. * * @type {number} */ redirects: 5, /** * The `withCredentials` option of XMLHttpRequest. * Set this to `true`