UNPKG

json-schema-describes-subset

Version:

Tools for static JSON schema analysis, including functions to determine if one schema describes a subset of another or if a schema describes the empty set or to convert a schema to its disjunctive normal form (DNF).

137 lines 4.58 kB
import isString from 'lodash/isString.js'; import {} from '../json-schema/index.js'; import { mapTuple } from '../utils/type-helpers/index.js'; import { cloneData } from '../utils/clone-data/index.js'; import { traverseJSONSchema } from '../utils/traverse-json-schema/index.js'; export function normalizeURI(uri) { if (uri === undefined) { return undefined; } return uri.replace(/#\/?$/, ''); } // export function isNonRelativeURI(uri: string): boolean { // try { // new URL(uri) // return true // } catch { // return false // } // } /** * Returns a non relative uri or `undefined` * */ export function resolveSchemaIdValue(baseURI, $id) { if ($id === undefined) { if (baseURI === undefined) { return undefined; } try { return normalizeURI(new URL(baseURI).href); } catch { return undefined; } } if ($id.startsWith('urn:') || $id.startsWith('tag:')) { // `$id` already is non relative return normalizeURI($id); } try { return normalizeURI(new URL($id, baseURI).href); } catch { /* The uri being invalid is only relevant upon encountering a relative * `$ref` or relative `$dynamicRef` => don't throw */ return undefined; } } export function createSchemaCloneWithNonRelativeIdsAndRefs(schema, baseURI, idDenyList) { const clonedSchema = cloneData(schema); const ids = new Set(); traverseJSONSchema(clonedSchema, (schema, baseURI, { parent }) => { const { $id } = schema; let newId; if (isString($id) || (parent === null && baseURI !== undefined)) { newId = resolveSchemaIdValue(baseURI, isString($id) ? $id : undefined); if (newId !== undefined && !idDenyList.has(newId)) { schema.$id = newId; ids.add(newId); } else { // remove relative `$id`s that cannot be resolved delete schema.$id; } } for (const refKeyword of ['$ref', '$dynamicRef']) { const refValue = schema[refKeyword]; if (!isString(refValue)) { continue; } const newRefValue = resolveSchemaIdValue(newId ?? baseURI, refValue); if (newRefValue !== undefined) { schema[refKeyword] = newRefValue; } } return newId ?? baseURI; }, baseURI); return { schema: clonedSchema, ids, }; } export function getBaseURIsFromOptions(options) { const baseURI = options?.baseURI; if (Array.isArray(baseURI)) { return baseURI.map((baseURI) => baseURI ?? undefined); } return [baseURI]; } /** * Takes `options`' `baseURI` property and returns the given `schemas` with a * `$id` that has been resolved accordingly. The returned `options`' `baseURI` * is set to `undefined` since this is not needed any more. * */ export function resolveSchemaArgumentsIds(schemas, options) { const baseURIs = getBaseURIsFromOptions(options); const includedIds = new Set(); const resolvedSchemas = mapTuple(schemas, (schema, index) => { const baseURI = baseURIs[index]; if (typeof schema === 'boolean') { try { /* if possible add baseURI as $id */ return { $id: normalizeURI(new URL(baseURI ?? '').href), allOf: [schema], }; } catch { return schema; } } const { schema: clonedSchema, ids } = createSchemaCloneWithNonRelativeIdsAndRefs(schema, baseURI, /* don't include $ids that were already included in other schemas, * this might affect JSON pointers that 'move up' and out of resource * scope, which shouldn't be possible in draft 2020-12 anyways */ includedIds); ids.forEach((id) => includedIds.add(id)); return clonedSchema; }); const schemasWithoutIds = mapTuple(resolvedSchemas, (schema) => { const clonedSchema = cloneData(schema); traverseJSONSchema(clonedSchema, (schema) => { delete schema.$id; }, undefined); return clonedSchema; }); return { schemas: schemasWithoutIds, options: { ...options, baseURI: undefined, definitions: [...resolvedSchemas, ...(options?.definitions ?? [])], }, }; } //# sourceMappingURL=id.js.map