| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- "use strict";
- module.exports = $Ref;
- const Pointer = require("./pointer");
- const { InvalidPointerError, isHandledError, normalizeError } = require("./util/errors");
- const { safePointerToPath, stripHash, getHash } = require("./util/url");
- /**
- * 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;
- }
- };
|