UNPKG

@bcherny/json-schema-ref-parser

Version:

Parse, Resolve, and Dereference JSON Schema $ref pointers

334 lines (333 loc) 12.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "default", { enumerable: true, get: function() { return _default; } }); var _pointerJs = /*#__PURE__*/ _interopRequireDefault(require("./pointer.js")); var _errorsJs = require("./util/errors.js"); var _urlJs = require("./util/url.js"); function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i]; return arr2; } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _instanceof(left, right) { if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } var _default = $Ref; /** * 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 = []; } var existingErrors = this.errors.map(function(param) { var footprint = param.footprint; return 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)) { var _this_errors; (_this_errors = this.errors).push.apply(_this_errors, _toConsumableArray(err.errors.map(_errorsJs.normalizeError).filter(function(param) { var footprint = param.footprint; return !existingErrors.includes(footprint); }))); } else if (!existingErrors.includes(err.footprint)) { this.errors.push((0, _errorsJs.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) { var pointer = new _pointerJs.default(this, path, friendlyPath); try { return pointer.resolve(this.value, options, pathFromRoot); } catch (err) { if (!options || !options.continueOnError || !(0, _errorsJs.isHandledError)(err)) { throw err; } if (err.path === null) { err.path = (0, _urlJs.safePointerToPath)((0, _urlJs.getHash)(pathFromRoot)); } if (_instanceof(err, _errorsJs.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((0, _urlJs.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) { var pointer = new _pointerJs.default(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)) { var merged = {}; var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined; try { for(var _iterator = Object.keys($ref)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){ var key = _step.value; if (key !== "$ref") { merged[key] = $ref[key]; } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally{ try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally{ if (_didIteratorError) { throw _iteratorError; } } } var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined; try { for(var _iterator1 = Object.keys(resolvedValue)[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){ var key1 = _step1.value; if (!(key1 in merged)) { merged[key1] = resolvedValue[key1]; } } } catch (err) { _didIteratorError1 = true; _iteratorError1 = err; } finally{ try { if (!_iteratorNormalCompletion1 && _iterator1.return != null) { _iterator1.return(); } } finally{ if (_didIteratorError1) { throw _iteratorError1; } } } return merged; } else { // Completely replace the original reference with the resolved value return resolvedValue; } };