UNPKG

@snups/rjsf-utils

Version:
160 lines 7.8 kB
import jsonpointer from 'jsonpointer'; import omit from 'lodash-es/omit.js'; import { ALL_OF_KEY, ID_KEY, JSON_SCHEMA_DRAFT_2019_09, JSON_SCHEMA_DRAFT_2020_12, REF_KEY, SCHEMA_KEY, } from './constants.js'; import isObject from 'lodash-es/isObject.js'; import isEmpty from 'lodash-es/isEmpty.js'; import UriResolver from 'fast-uri'; import get from 'lodash-es/get.js'; /** Looks for the `$id` pointed by `ref` in the schema definitions embedded in * a JSON Schema bundle * * @param schema - The schema wherein `ref` should be searched * @param ref - The `$id` of the reference to search for * @returns - The schema matching the reference, or `undefined` if no match is found */ function findEmbeddedSchemaRecursive(schema, ref) { if (ID_KEY in schema && UriResolver.equal(schema[ID_KEY], ref)) { return schema; } for (const subSchema of Object.values(schema)) { if (Array.isArray(subSchema)) { for (const item of subSchema) { if (isObject(item)) { const result = findEmbeddedSchemaRecursive(item, ref); if (result !== undefined) { return result; } } } } else if (isObject(subSchema)) { const result = findEmbeddedSchemaRecursive(subSchema, ref); if (result !== undefined) { return result; } } } return undefined; } /** Parses a JSONSchema and makes all references absolute with respect to * the `baseURI` argument * @param schema - The schema to be processed * @param baseURI - The base URI to be used for resolving relative references */ export function makeAllReferencesAbsolute(schema, baseURI) { const currentURI = get(schema, ID_KEY, baseURI); // Make all other references absolute if (REF_KEY in schema) { schema = { ...schema, [REF_KEY]: UriResolver.resolve(currentURI, schema[REF_KEY]) }; } // Look for references in nested subschemas for (const [key, subSchema] of Object.entries(schema)) { if (Array.isArray(subSchema)) { schema = { ...schema, [key]: subSchema.map((item) => (isObject(item) ? makeAllReferencesAbsolute(item, currentURI) : item)), }; } else if (isObject(subSchema)) { schema = { ...schema, [key]: makeAllReferencesAbsolute(subSchema, currentURI) }; } } return schema; } /** Splits out the value at the `key` in `object` from the `object`, returning an array that contains in the first * location, the `object` minus the `key: value` and in the second location the `value`. * * @param key - The key from the object to extract * @param object - The object from which to extract the element * @returns - An array with the first value being the object minus the `key` element and the second element being the * value from `object[key]` */ export function splitKeyElementFromObject(key, object) { const value = object[key]; const remaining = omit(object, [key]); return [remaining, value]; } /** Given the name of a `$ref` from within a schema, using the `rootSchema`, recursively look up and return the * sub-schema using the path provided by that reference. If `#` is not the first character of the reference, the path * does not exist in the schema, or the reference resolves circularly back to itself, then throw an Error. * Otherwise return the sub-schema. Also deals with nested `$ref`s in the sub-schema. * * @param $ref - The ref string for which the schema definition is desired * @param [rootSchema={}] - The root schema in which to search for the definition * @param recurseList - List of $refs already resolved to prevent recursion * @param [baseURI=rootSchema['$id']] - The base URI to be used for resolving relative references * @returns - The sub-schema within the `rootSchema` which matches the `$ref` if it exists * @throws - Error indicating that no schema for that reference could be resolved */ export function findSchemaDefinitionRecursive($ref, rootSchema = {}, recurseList = [], baseURI = get(rootSchema, [ID_KEY])) { const ref = $ref || ''; let current = undefined; if (ref.startsWith('#')) { // Decode URI fragment representation. const decodedRef = decodeURIComponent(ref.substring(1)); if (baseURI === undefined || (ID_KEY in rootSchema && rootSchema[ID_KEY] === baseURI)) { current = jsonpointer.get(rootSchema, decodedRef); } else if (rootSchema[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2020_12) { current = findEmbeddedSchemaRecursive(rootSchema, baseURI.replace(/\/$/, '')); if (current !== undefined) { current = jsonpointer.get(current, decodedRef); } } } else if (rootSchema[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2020_12) { const resolvedRef = baseURI ? UriResolver.resolve(baseURI, ref) : ref; const [refId, ...refAnchor] = resolvedRef.replace(/#\/?$/, '').split('#'); current = findEmbeddedSchemaRecursive(rootSchema, refId.replace(/\/$/, '')); if (current !== undefined) { baseURI = current[ID_KEY]; if (!isEmpty(refAnchor)) { current = jsonpointer.get(current, decodeURIComponent(refAnchor.join('#'))); } } } if (current === undefined) { throw new Error(`Could not find a definition for ${$ref}.`); } const nextRef = current[REF_KEY]; if (nextRef) { // Check for circular references. if (recurseList.includes(nextRef)) { if (recurseList.length === 1) { throw new Error(`Definition for ${$ref} is a circular reference`); } const [firstRef, ...restRefs] = recurseList; const circularPath = [...restRefs, ref, firstRef].join(' -> '); throw new Error(`Definition for ${firstRef} contains a circular reference through ${circularPath}`); } const [remaining, theRef] = splitKeyElementFromObject(REF_KEY, current); const subSchema = findSchemaDefinitionRecursive(theRef, rootSchema, [...recurseList, ref], baseURI); if (Object.keys(remaining).length > 0) { if (rootSchema[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2019_09 || rootSchema[SCHEMA_KEY] === JSON_SCHEMA_DRAFT_2020_12) { return { [ALL_OF_KEY]: [remaining, subSchema] }; } else { return { ...remaining, ...subSchema }; } } return subSchema; } return current; } /** Given the name of a `$ref` from within a schema, using the `rootSchema`, look up and return the sub-schema using the * path provided by that reference. If `#` is not the first character of the reference, the path does not exist in * the schema, or the reference resolves circularly back to itself, then throw an Error. Otherwise return the * sub-schema. Also deals with nested `$ref`s in the sub-schema. * * @param $ref - The ref string for which the schema definition is desired * @param [rootSchema={}] - The root schema in which to search for the definition * @param [baseURI=rootSchema['$id']] - The base URI to be used for resolving relative references * @returns - The sub-schema within the `rootSchema` which matches the `$ref` if it exists * @throws - Error indicating that no schema for that reference could be resolved */ export default function findSchemaDefinition($ref, rootSchema = {}, baseURI = get(rootSchema, [ID_KEY])) { const recurseList = []; return findSchemaDefinitionRecursive($ref, rootSchema, recurseList, baseURI); } //# sourceMappingURL=findSchemaDefinition.js.map