UNPKG

json-schema-ref-parser

Version:

Parse, Resolve, and Dereference JSON Schema $ref pointers

1,386 lines (1,236 loc) 443 kB
/*! * JSON Schema $Ref Parser v6.1.0 (February 21st 2019) * * https://apidevtools.org/json-schema-ref-parser/ * * @author James Messinger (https://jamesmessinger.com) * @license MIT */ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.$RefParser = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){ "use strict"; var $Ref = require("./ref"), Pointer = require("./pointer"), url = require("./util/url"); module.exports = bundle; /** * Bundles all external JSON references into the main JSON schema, thus resulting in a schema that * only has *internal* references, not any *external* references. * This method mutates the JSON schema object, adding new references and re-mapping existing ones. * * @param {$RefParser} parser * @param {$RefParserOptions} options */ function bundle (parser, options) { // console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path); // Build an inventory of all $ref pointers in the JSON Schema var inventory = []; crawl(parser, "schema", parser.$refs._root$Ref.path + "#", "#", 0, inventory, parser.$refs, options); // Remap all $ref pointers remap(inventory); } /** * Recursively crawls the given value, and inventories all JSON references. * * @param {object} parent - The object containing the value to crawl. If the value is not an object or array, it will be ignored. * @param {string} key - The property key of `parent` to be crawled * @param {string} path - The full path of the property being crawled, possibly with a JSON Pointer in the hash * @param {string} pathFromRoot - The path of the property being crawled, from the schema root * @param {object[]} inventory - An array of already-inventoried $ref pointers * @param {$Refs} $refs * @param {$RefParserOptions} options */ function crawl (parent, key, path, pathFromRoot, indirections, inventory, $refs, options) { var obj = key === null ? parent : parent[key]; if (obj && typeof obj === "object") { if ($Ref.isAllowed$Ref(obj)) { inventory$Ref(parent, key, path, pathFromRoot, indirections, inventory, $refs, options); } else { // Crawl the object in a specific order that's optimized for bundling. // This is important because it determines how `pathFromRoot` gets built, // which later determines which keys get dereferenced and which ones get remapped var keys = Object.keys(obj) .sort(function (a, b) { // Most people will expect references to be bundled into the the "definitions" property, // so we always crawl that property first, if it exists. if (a === "definitions") { return -1; } else if (b === "definitions") { return 1; } else { // Otherwise, crawl the keys based on their length. // This produces the shortest possible bundled references return a.length - b.length; } }); keys.forEach(function (key) { var keyPath = Pointer.join(path, key); var keyPathFromRoot = Pointer.join(pathFromRoot, key); var value = obj[key]; if ($Ref.isAllowed$Ref(value)) { inventory$Ref(obj, key, path, keyPathFromRoot, indirections, inventory, $refs, options); } else { crawl(obj, key, keyPath, keyPathFromRoot, indirections, inventory, $refs, options); } }); } } } /** * Inventories the given JSON Reference (i.e. records detailed information about it so we can * optimize all $refs in the schema), and then crawls the resolved value. * * @param {object} $refParent - The object that contains a JSON Reference as one of its keys * @param {string} $refKey - The key in `$refParent` that is a JSON Reference * @param {string} path - The full path of the JSON Reference at `$refKey`, possibly with a JSON Pointer in the hash * @param {string} pathFromRoot - The path of the JSON Reference at `$refKey`, from the schema root * @param {object[]} inventory - An array of already-inventoried $ref pointers * @param {$Refs} $refs * @param {$RefParserOptions} options */ function inventory$Ref ($refParent, $refKey, path, pathFromRoot, indirections, inventory, $refs, options) { var $ref = $refKey === null ? $refParent : $refParent[$refKey]; var $refPath = url.resolve(path, $ref.$ref); var pointer = $refs._resolve($refPath, options); var depth = Pointer.parse(pathFromRoot).length; var file = url.stripHash(pointer.path); var hash = url.getHash(pointer.path); var external = file !== $refs._root$Ref.path; var extended = $Ref.isExtended$Ref($ref); indirections += pointer.indirections; var existingEntry = findInInventory(inventory, $refParent, $refKey); if (existingEntry) { // This $Ref has already been inventoried, so we don't need to process it again if (depth < existingEntry.depth || indirections < existingEntry.indirections) { removeFromInventory(inventory, existingEntry); } else { return; } } inventory.push({ $ref: $ref, // The JSON Reference (e.g. {$ref: string}) parent: $refParent, // The object that contains this $ref pointer key: $refKey, // The key in `parent` that is the $ref pointer pathFromRoot: pathFromRoot, // The path to the $ref pointer, from the JSON Schema root depth: depth, // How far from the JSON Schema root is this $ref pointer? file: file, // The file that the $ref pointer resolves to hash: hash, // The hash within `file` that the $ref pointer resolves to value: pointer.value, // The resolved value of the $ref pointer circular: pointer.circular, // Is this $ref pointer DIRECTLY circular? (i.e. it references itself) extended: extended, // Does this $ref extend its resolved value? (i.e. it has extra properties, in addition to "$ref") external: external, // Does this $ref pointer point to a file other than the main JSON Schema file? indirections: indirections, // The number of indirect references that were traversed to resolve the value }); // Recursively crawl the resolved value crawl(pointer.value, null, pointer.path, pathFromRoot, indirections + 1, inventory, $refs, options); } /** * Re-maps every $ref pointer, so that they're all relative to the root of the JSON Schema. * Each referenced value is dereferenced EXACTLY ONCE. All subsequent references to the same * value are re-mapped to point to the first reference. * * @example: * { * first: { $ref: somefile.json#/some/part }, * second: { $ref: somefile.json#/another/part }, * third: { $ref: somefile.json }, * fourth: { $ref: somefile.json#/some/part/sub/part } * } * * In this example, there are four references to the same file, but since the third reference points * to the ENTIRE file, that's the only one we need to dereference. The other three can just be * remapped to point inside the third one. * * On the other hand, if the third reference DIDN'T exist, then the first and second would both need * to be dereferenced, since they point to different parts of the file. The fourth reference does NOT * need to be dereferenced, because it can be remapped to point inside the first one. * * @param {object[]} inventory */ function remap (inventory) { // Group & sort all the $ref pointers, so they're in the order that we need to dereference/remap them inventory.sort(function (a, b) { if (a.file !== b.file) { // Group all the $refs that point to the same file return a.file < b.file ? -1 : +1; } else if (a.hash !== b.hash) { // Group all the $refs that point to the same part of the file return a.hash < b.hash ? -1 : +1; } else if (a.circular !== b.circular) { // If the $ref points to itself, then sort it higher than other $refs that point to this $ref return a.circular ? -1 : +1; } else if (a.extended !== b.extended) { // If the $ref extends the resolved value, then sort it lower than other $refs that don't extend the value return a.extended ? +1 : -1; } else if (a.indirections !== b.indirections) { // Sort direct references higher than indirect references return a.indirections - b.indirections; } else if (a.depth !== b.depth) { // Sort $refs by how close they are to the JSON Schema root return a.depth - b.depth; } else { // Determine how far each $ref is from the "definitions" property. // Most people will expect references to be bundled into the the "definitions" property if possible. var aDefinitionsIndex = a.pathFromRoot.lastIndexOf("/definitions"); var bDefinitionsIndex = b.pathFromRoot.lastIndexOf("/definitions"); if (aDefinitionsIndex !== bDefinitionsIndex) { // Give higher priority to the $ref that's closer to the "definitions" property return bDefinitionsIndex - aDefinitionsIndex; } else { // All else is equal, so use the shorter path, which will produce the shortest possible reference return a.pathFromRoot.length - b.pathFromRoot.length; } } }); var file, hash, pathFromRoot; inventory.forEach(function (entry) { // console.log('Re-mapping $ref pointer "%s" at %s', entry.$ref.$ref, entry.pathFromRoot); if (!entry.external) { // This $ref already resolves to the main JSON Schema file entry.$ref.$ref = entry.hash; } else if (entry.file === file && entry.hash === hash) { // This $ref points to the same value as the prevous $ref, so remap it to the same path entry.$ref.$ref = pathFromRoot; } else if (entry.file === file && entry.hash.indexOf(hash + "/") === 0) { // This $ref points to a sub-value of the prevous $ref, so remap it beneath that path entry.$ref.$ref = Pointer.join(pathFromRoot, Pointer.parse(entry.hash.replace(hash, "#"))); } else { // We've moved to a new file or new hash file = entry.file; hash = entry.hash; pathFromRoot = entry.pathFromRoot; // This is the first $ref to point to this value, so dereference the value. // Any other $refs that point to the same value will point to this $ref instead entry.$ref = entry.parent[entry.key] = $Ref.dereference(entry.$ref, entry.value); if (entry.circular) { // This $ref points to itself entry.$ref.$ref = entry.pathFromRoot; } } // console.log(' new value: %s', (entry.$ref && entry.$ref.$ref) ? entry.$ref.$ref : '[object Object]'); }); } /** * TODO */ function findInInventory (inventory, $refParent, $refKey) { for (var i = 0; i < inventory.length; i++) { var existingEntry = inventory[i]; if (existingEntry.parent === $refParent && existingEntry.key === $refKey) { return existingEntry; } } } function removeFromInventory (inventory, entry) { var index = inventory.indexOf(entry); inventory.splice(index, 1); } },{"./pointer":11,"./ref":12,"./util/url":18}],2:[function(require,module,exports){ "use strict"; var $Ref = require("./ref"), Pointer = require("./pointer"), ono = require("ono"), url = require("./util/url"); module.exports = dereference; /** * Crawls the JSON schema, finds all JSON references, and dereferences them. * This method mutates the JSON schema object, replacing JSON references with their resolved value. * * @param {$RefParser} parser * @param {$RefParserOptions} options */ function dereference (parser, options) { // console.log('Dereferencing $ref pointers in %s', parser.$refs._root$Ref.path); var dereferenced = crawl(parser.schema, parser.$refs._root$Ref.path, "#", [], parser.$refs, options); parser.$refs.circular = dereferenced.circular; parser.schema = dereferenced.value; } /** * Recursively crawls the given value, and dereferences any 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 {string} pathFromRoot - The path of `obj` from the schema root * @param {object[]} parents - An array of the parent objects that have already been dereferenced * @param {$Refs} $refs * @param {$RefParserOptions} options * @returns {{value: object, circular: boolean}} */ function crawl (obj, path, pathFromRoot, parents, $refs, options) { var dereferenced; var result = { value: obj, circular: false }; if (obj && typeof obj === "object") { parents.push(obj); if ($Ref.isAllowed$Ref(obj, options)) { dereferenced = dereference$Ref(obj, path, pathFromRoot, parents, $refs, options); result.circular = dereferenced.circular; result.value = dereferenced.value; } else { Object.keys(obj).forEach(function (key) { var keyPath = Pointer.join(path, key); var keyPathFromRoot = Pointer.join(pathFromRoot, key); var value = obj[key]; var circular = false; if ($Ref.isAllowed$Ref(value, options)) { dereferenced = dereference$Ref(value, keyPath, keyPathFromRoot, parents, $refs, options); circular = dereferenced.circular; obj[key] = dereferenced.value; } else { if (parents.indexOf(value) === -1) { dereferenced = crawl(value, keyPath, keyPathFromRoot, parents, $refs, options); circular = dereferenced.circular; obj[key] = dereferenced.value; } else { circular = foundCircularReference(keyPath, $refs, options); } } // Set the "isCircular" flag if this or any other property is circular result.circular = result.circular || circular; }); } parents.pop(); } return result; } /** * Dereferences 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 {string} pathFromRoot - The path of `$ref` from the schema root * @param {object[]} parents - An array of the parent objects that have already been dereferenced * @param {$Refs} $refs * @param {$RefParserOptions} options * @returns {{value: object, circular: boolean}} */ function dereference$Ref ($ref, path, pathFromRoot, parents, $refs, options) { // console.log('Dereferencing $ref pointer "%s" at %s', $ref.$ref, path); var $refPath = url.resolve(path, $ref.$ref); var pointer = $refs._resolve($refPath, options); // Check for circular references var directCircular = pointer.circular; var circular = directCircular || parents.indexOf(pointer.value) !== -1; circular && foundCircularReference(path, $refs, options); // Dereference the JSON reference var dereferencedValue = $Ref.dereference($ref, pointer.value); // Crawl the dereferenced value (unless it's circular) if (!circular) { // Determine if the dereferenced value is circular var dereferenced = crawl(dereferencedValue, pointer.path, pathFromRoot, parents, $refs, options); circular = dereferenced.circular; dereferencedValue = dereferenced.value; } if (circular && !directCircular && options.dereference.circular === "ignore") { // The user has chosen to "ignore" circular references, so don't change the value dereferencedValue = $ref; } if (directCircular) { // The pointer is a DIRECT circular reference (i.e. it references itself). // So replace the $ref path with the absolute path from the JSON Schema root dereferencedValue.$ref = pathFromRoot; } return { circular: circular, value: dereferencedValue }; } /** * Called when a circular reference is found. * It sets the {@link $Refs#circular} flag, and throws an error if options.dereference.circular is false. * * @param {string} keyPath - The JSON Reference path of the circular reference * @param {$Refs} $refs * @param {$RefParserOptions} options * @returns {boolean} - always returns true, to indicate that a circular reference was found */ function foundCircularReference (keyPath, $refs, options) { $refs.circular = true; if (!options.dereference.circular) { throw ono.reference("Circular $ref pointer found at %s", keyPath); } return true; } },{"./pointer":11,"./ref":12,"./util/url":18,"ono":64}],3:[function(require,module,exports){ (function (Buffer){ "use strict"; var Options = require("./options"), $Refs = require("./refs"), parse = require("./parse"), normalizeArgs = require("./normalize-args"), resolveExternal = require("./resolve-external"), bundle = require("./bundle"), dereference = require("./dereference"), url = require("./util/url"), maybe = require("call-me-maybe"), ono = require("ono"); module.exports = $RefParser; module.exports.YAML = require("./util/yaml"); /** * This class parses a JSON schema, builds a map of its JSON references and their resolved values, * and provides methods for traversing, manipulating, and dereferencing those references. * * @constructor */ function $RefParser () { /** * The parsed (and possibly dereferenced) JSON schema object * * @type {object} * @readonly */ this.schema = null; /** * The resolved JSON references * * @type {$Refs} * @readonly */ this.$refs = new $Refs(); } /** * Parses the given JSON schema. * This method does not resolve any JSON references. * It just reads a single file in JSON or YAML format, and parse it as a JavaScript object. * * @param {string} [path] - The file path or URL of the JSON schema * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed * @param {function} [callback] - An error-first callback. The second parameter is the parsed JSON schema object. * @returns {Promise} - The returned promise resolves with the parsed JSON schema object. */ $RefParser.parse = function (path, schema, options, callback) { var Class = this; // eslint-disable-line consistent-this var instance = new Class(); return instance.parse.apply(instance, arguments); }; /** * Parses the given JSON schema. * This method does not resolve any JSON references. * It just reads a single file in JSON or YAML format, and parse it as a JavaScript object. * * @param {string} [path] - The file path or URL of the JSON schema * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed * @param {function} [callback] - An error-first callback. The second parameter is the parsed JSON schema object. * @returns {Promise} - The returned promise resolves with the parsed JSON schema object. */ $RefParser.prototype.parse = function (path, schema, options, callback) { var args = normalizeArgs(arguments); var promise; if (!args.path && !args.schema) { var err = ono("Expected a file path, URL, or object. Got %s", args.path || args.schema); return maybe(args.callback, Promise.reject(err)); } // Reset everything this.schema = null; this.$refs = new $Refs(); // If the path is a filesystem path, then convert it to a URL. // NOTE: According to the JSON Reference spec, these should already be URLs, // but, in practice, many people use local filesystem paths instead. // So we're being generous here and doing the conversion automatically. // This is not intended to be a 100% bulletproof solution. // If it doesn't work for your use-case, then use a URL instead. var pathType = "http"; if (url.isFileSystemPath(args.path)) { args.path = url.fromFileSystemPath(args.path); pathType = "file"; } // Resolve the absolute path of the schema args.path = url.resolve(url.cwd(), args.path); if (args.schema && typeof args.schema === "object") { // A schema object was passed-in. // So immediately add a new $Ref with the schema object as its value var $ref = this.$refs._add(args.path); $ref.value = args.schema; $ref.pathType = pathType; promise = Promise.resolve(args.schema); } else { // Parse the schema file/url promise = parse(args.path, this.$refs, args.options); } var me = this; return promise .then(function (result) { if (!result || typeof result !== "object" || Buffer.isBuffer(result)) { throw ono.syntax('"%s" is not a valid JSON Schema', me.$refs._root$Ref.path || result); } else { me.schema = result; return maybe(args.callback, Promise.resolve(me.schema)); } }) .catch(function (e) { return maybe(args.callback, Promise.reject(e)); }); }; /** * Parses the given JSON schema and resolves any JSON references, including references in * externally-referenced files. * * @param {string} [path] - The file path or URL of the JSON schema * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed and resolved * @param {function} [callback] * - An error-first callback. The second parameter is a {@link $Refs} object containing the resolved JSON references * * @returns {Promise} * The returned promise resolves with a {@link $Refs} object containing the resolved JSON references */ $RefParser.resolve = function (path, schema, options, callback) { var Class = this; // eslint-disable-line consistent-this var instance = new Class(); return instance.resolve.apply(instance, arguments); }; /** * Parses the given JSON schema and resolves any JSON references, including references in * externally-referenced files. * * @param {string} [path] - The file path or URL of the JSON schema * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed and resolved * @param {function} [callback] * - An error-first callback. The second parameter is a {@link $Refs} object containing the resolved JSON references * * @returns {Promise} * The returned promise resolves with a {@link $Refs} object containing the resolved JSON references */ $RefParser.prototype.resolve = function (path, schema, options, callback) { var me = this; var args = normalizeArgs(arguments); return this.parse(args.path, args.schema, args.options) .then(function () { return resolveExternal(me, args.options); }) .then(function () { return maybe(args.callback, Promise.resolve(me.$refs)); }) .catch(function (err) { return maybe(args.callback, Promise.reject(err)); }); }; /** * Parses the given JSON schema, resolves any JSON references, and bundles all external references * into the main JSON schema. This produces a JSON schema that only has *internal* references, * not any *external* references. * * @param {string} [path] - The file path or URL of the JSON schema * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed, resolved, and dereferenced * @param {function} [callback] - An error-first callback. The second parameter is the bundled JSON schema object * @returns {Promise} - The returned promise resolves with the bundled JSON schema object. */ $RefParser.bundle = function (path, schema, options, callback) { var Class = this; // eslint-disable-line consistent-this var instance = new Class(); return instance.bundle.apply(instance, arguments); }; /** * Parses the given JSON schema, resolves any JSON references, and bundles all external references * into the main JSON schema. This produces a JSON schema that only has *internal* references, * not any *external* references. * * @param {string} [path] - The file path or URL of the JSON schema * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed, resolved, and dereferenced * @param {function} [callback] - An error-first callback. The second parameter is the bundled JSON schema object * @returns {Promise} - The returned promise resolves with the bundled JSON schema object. */ $RefParser.prototype.bundle = function (path, schema, options, callback) { var me = this; var args = normalizeArgs(arguments); return this.resolve(args.path, args.schema, args.options) .then(function () { bundle(me, args.options); return maybe(args.callback, Promise.resolve(me.schema)); }) .catch(function (err) { return maybe(args.callback, Promise.reject(err)); }); }; /** * Parses the given JSON schema, resolves any JSON references, and dereferences the JSON schema. * That is, all JSON references are replaced with their resolved values. * * @param {string} [path] - The file path or URL of the JSON schema * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed, resolved, and dereferenced * @param {function} [callback] - An error-first callback. The second parameter is the dereferenced JSON schema object * @returns {Promise} - The returned promise resolves with the dereferenced JSON schema object. */ $RefParser.dereference = function (path, schema, options, callback) { var Class = this; // eslint-disable-line consistent-this var instance = new Class(); return instance.dereference.apply(instance, arguments); }; /** * Parses the given JSON schema, resolves any JSON references, and dereferences the JSON schema. * That is, all JSON references are replaced with their resolved values. * * @param {string} [path] - The file path or URL of the JSON schema * @param {object} [schema] - A JSON schema object. This object will be used instead of reading from `path`. * @param {$RefParserOptions} [options] - Options that determine how the schema is parsed, resolved, and dereferenced * @param {function} [callback] - An error-first callback. The second parameter is the dereferenced JSON schema object * @returns {Promise} - The returned promise resolves with the dereferenced JSON schema object. */ $RefParser.prototype.dereference = function (path, schema, options, callback) { var me = this; var args = normalizeArgs(arguments); return this.resolve(args.path, args.schema, args.options) .then(function () { dereference(me, args.options); return maybe(args.callback, Promise.resolve(me.schema)); }) .catch(function (err) { return maybe(args.callback, Promise.reject(err)); }); }; }).call(this,{"isBuffer":require("../node_modules/is-buffer/index.js")}) },{"../node_modules/is-buffer/index.js":32,"./bundle":1,"./dereference":2,"./normalize-args":4,"./options":5,"./parse":6,"./refs":13,"./resolve-external":14,"./util/url":18,"./util/yaml":19,"call-me-maybe":25,"ono":64}],4:[function(require,module,exports){ "use strict"; var Options = require("./options"); module.exports = normalizeArgs; /** * Normalizes the given arguments, accounting for optional args. * * @param {Arguments} args * @returns {object} */ function normalizeArgs (args) { var 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: path, schema: schema, options: options, callback: callback }; } },{"./options":5}],5:[function(require,module,exports){ /* eslint lines-around-comment: [2, {beforeBlockComment: false}] */ "use strict"; var jsonParser = require("./parsers/json"), yamlParser = require("./parsers/yaml"), textParser = require("./parsers/text"), binaryParser = require("./parsers/binary"), fileResolver = require("./resolvers/file"), httpResolver = require("./resolvers/http"); 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 implemenation, 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 implemenation, 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, }, /** * 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 }, }; /** * 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)) { var keys = Object.keys(source); for (var i = 0; i < keys.length; i++) { var key = keys[i]; var sourceSetting = source[key]; var 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); } },{"./parsers/binary":7,"./parsers/json":8,"./parsers/text":9,"./parsers/yaml":10,"./resolvers/file":15,"./resolvers/http":16}],6:[function(require,module,exports){ (function (Buffer){ "use strict"; var ono = require("ono"), url = require("./util/url"), plugins = require("./util/plugins"); 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. */ function parse (path, $refs, options) { try { // 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 var $ref = $refs._add(path); // This "file object" will be passed to all resolvers and parsers. var file = { url: path, extension: url.getExtension(path), }; // Read the file and then parse the data return readFile(file, options) .then(function (resolver) { $ref.pathType = resolver.plugin.name; file.data = resolver.result; return parseFile(file, options); }) .then(function (parser) { $ref.value = parser.result; return parser.result; }); } catch (e) { return Promise.reject(e); } } /** * 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) { return new Promise(function (resolve, reject) { // console.log('Reading %s', file.url); // Find the resolvers that can read this file var 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) .then(resolve, onError); function onError (err) { // Throw the original error, if it's one of our own (user-friendly) errors. // Otherwise, throw a generic, friendly error. if (err && !(err instanceof SyntaxError)) { reject(err); } else { reject(ono.syntax('Unable to resolve $ref pointer "%s"', 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) { return new Promise(function (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. var allParsers = plugins.all(options.parse); var filteredParsers = plugins.filter(allParsers, "canParse", file); var parsers = filteredParsers.length > 0 ? filteredParsers : allParsers; // Run the parsers, in order, until one of them succeeds plugins.sort(parsers); plugins.run(parsers, "parse", file) .then(onParsed, onError); function onParsed (parser) { if (!parser.plugin.allowEmpty && isEmpty(parser.result)) { reject(ono.syntax('Error parsing "%s" as %s. \nParsed value is empty', file.url, parser.plugin.name)); } else { resolve(parser); } } function onError (err) { if (err) { err = err instanceof Error ? err : new Error(err); reject(ono.syntax(err, "Error parsing %s", file.url)); } else { reject(ono.syntax("Unable to parse %s", 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); } }).call(this,{"isBuffer":require("../node_modules/is-buffer/index.js")}) },{"../node_modules/is-buffer/index.js":32,"./util/plugins":17,"./util/url":18,"ono":64}],7:[function(require,module,exports){ (function (Buffer){ "use strict"; var 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: function isBinary (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 {Promise<Buffer>} */ parse: function parseBinary (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 new Buffer(file.data); } } }; }).call(this,require("buffer").Buffer) },{"buffer":23}],8:[function(require,module,exports){ (function (Buffer){ "use strict"; 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[]|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} */ parse: function parseJSON (file) { return new Promise(function (resolve, reject) { var data = file.data; if (Buffer.isBuffer(data)) { data = data.toString(); } if (typeof data === "string") { if (data.trim().length === 0) { resolve(undefined); // This mirrors the YAML behavior } else { resolve(JSON.parse(data)); } } else { // data is already a JavaScript value (object, array, number, null, NaN, etc.) resolve(data); } }); } }; }).call(this,{"isBuffer":require("../../node_modules/is-buffer/index.js")}) },{"../../node_modules/is-buffer/index.js":32}],9:[function(require,module,exports){ (function (Buffer){ "use strict"; var 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: function isText (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 {Promise<string>} */ parse: function parseText (file) { if (typeof file.data === "string") { return file.data; } else if (Buffer.isBuffer(file.data)) { return file.data.toString(this.encoding); } else { throw new Error("data is not text"); } } }; }).call(this,{"isBuffer":require("../../node_modules/is-buffer/index.js")}) },{"../../node_modules/is-buffer/index.js":32}],10:[function(require,module,exports){ (function (Buffer){ "use strict"; var YAML = require("../util/yaml"); 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} */ parse: function parseYAML (file) { return new Promise(function (resolve, reject) { var data = file.data; if (Buffer.isBuffer(data)) { data = data.toString(); } if (typeof data === "string") { resolve(YAML.parse(data)); } else { // data is already a JavaScript value (object, array, number, null, NaN, etc.) resolve(data); } }); } }; }).call(this,{"isBuffer":require("../../node_modules/is-buffer/index.js")}) },{"../../node_modules/is-buffer/index.js":32,"../util/yaml":19}],11:[function(require,module,exports){ "use strict"; module.exports = Pointer; var $Ref = require("./ref"), url = require("./util/url"), ono = require("ono"), slashes = /\//g, tildes = /~/g, escapedSlash = /~1/g, 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 * * @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) { var tokens = Pointer.parse(this.path); // Crawl the object, one token at a time this.value = obj; for (var 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)); } var token = tokens[i]; if (this.value[token] === undefined) { throw ono.syntax('Error resolving $ref pointer "%s". \nToken "%s" does not exist.', this.originalPath, token); } else { this.value = this.value[token]; } } // Resolve the final value 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) { var tokens = Pointer.parse(this.path); var 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 = obj; for (var 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 t