json-schema-library
Version:
Customizable and hackable json-validator and json-schema utilities for traversal, data generation and validation
1 lines • 413 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","names":["suffixes","settings","getDraft","REGEX_FLAGS","addKeywords","DECLARATOR_ONEOF","$refKeyword","parseRef","validateRef","register","resolveRef","resolveRecursiveRef","getRef","compileNext","settings","$refKeyword","parseRef","validateRef","resolveRef","$refKeyword","parseRef","draft06Keyword","register","resolveRef","getRef","KEYWORD","settings","KEYWORD","KEYWORD","KEYWORD","KEYWORD","KEYWORD","KEYWORD","KEYWORD","KEYWORD","exclusiveMaximumKeyword","validateExclusiveMaximum","parse","exclusiveMinimumKeyword","validateExclusiveMinimum","KEYWORD","settings","REGEX_FLAGS","getChildSelection","KEYWORD","settings","safeResolveRef","canResolveRef","convertValue","getData","TYPE","getDefault","KEYWORD","itemsKeyword","parseItems","itemsResolver","validateItems","KEYWORD","KEYWORD","KEYWORD","KEYWORD","KEYWORD","KEYWORD","parseMinItems","KEYWORD","KEYWORD","KEYWORD","KEYWORD","KEYWORD","settings","REGEX_FLAGS","settings","REGEX_FLAGS","KEYWORD","KEYWORD","KEYWORD","KEYWORD","$refKeyword","exclusiveMaximumKeyword","exclusiveMinimumKeyword","itemsKeyword","KEYWORD","KEYWORD","$refKeyword","itemsKeyword","$refKeyword","itemsKeyword","compileNext","KEYWORD","unevaluatedItemsKeyword","parseUnevaluatedItems","validateUnevaluatedItems","KEYWORD","itemsKeyword","unevaluatedItemsKeyword","KEYWORD","parseItems","KEYWORD","KEYWORD","$refKeyword","settings","getRef","addKeywords"],"sources":["../src/utils/sanitizeErrors.ts","../src/settings.ts","../src/utils/getTypeOf.ts","../src/utils/isObject.ts","../src/methods/createSchema.ts","../src/methods/toSchemaNodes.ts","../src/utils/resolveUri.ts","../src/utils/mergeSchema.ts","../src/mergeNode.ts","../src/utils/omit.ts","../src/utils/pick.ts","../src/errors/render.ts","../src/validateNode.ts","../src/utils/hasProperty.ts","../src/utils/getValue.ts","../src/getNode.ts","../src/getNodeChild.ts","../src/SchemaNode.ts","../src/types.ts","../src/utils/splitRef.ts","../src/keywords/$ref.ts","../src/utils/collectValidationErrors.ts","../src/keywords/$defs.ts","../src/draft06/keywords/$ref.ts","../src/draft04/keywords/$ref.ts","../src/draft2019-09/keywords/additionalItems.ts","../src/keywords/additionalProperties.ts","../src/keywords/allOf.ts","../src/keywords/anyOf.ts","../src/keywords/contains.ts","../src/utils/isListOfStrings.ts","../src/keywords/dependentRequired.ts","../src/keywords/dependentSchemas.ts","../src/keywords/dependencies.ts","../src/keywords/deprecated.ts","../src/keywords/enum.ts","../src/errors/errors.ts","../src/draft04/keywords/exclusiveMaximum.ts","../src/draft04/keywords/exclusiveMinimum.ts","../src/keywords/format.ts","../src/formats/formats.ts","../src/draft2019-09/methods/getChildSelection.ts","../src/utils/getSchemaType.ts","../src/utils/isEmpty.ts","../src/keywords/oneOf.ts","../src/utils/isFile.ts","../src/draft2019-09/methods/getData.ts","../src/draft2019-09/keywords/items.ts","../src/keywords/maximum.ts","../src/keywords/maxItems.ts","../src/utils/punycode.ucs2decode.ts","../src/keywords/maxLength.ts","../src/keywords/maxProperties.ts","../src/keywords/minimum.ts","../src/keywords/minItems.ts","../src/keywords/minLength.ts","../src/keywords/minProperties.ts","../src/utils/getPrecision.ts","../src/keywords/multipleOf.ts","../src/keywords/not.ts","../src/keywords/pattern.ts","../src/keywords/patternProperties.ts","../src/keywords/properties.ts","../src/keywords/propertyNames.ts","../src/keywords/required.ts","../src/utils/copyDraft.ts","../src/Draft.ts","../src/methods/toDataNodes.ts","../src/keywords/type.ts","../src/keywords/uniqueItems.ts","../src/draft04.ts","../src/keywords/const.ts","../src/keywords/exclusiveMaximum.ts","../src/keywords/exclusiveMinimum.ts","../src/draft06.ts","../src/keywords/ifthenelse.ts","../src/draft07.ts","../src/draft2019-09/keywords/$ref.ts","../src/draft2019-09/keywords/unevaluatedItems.ts","../src/isPropertyEvaluated.ts","../src/keywords/unevaluatedProperties.ts","../src/draft2019.ts","../src/methods/getChildSelection.ts","../src/methods/getData.ts","../src/keywords/items.ts","../src/keywords/prefixItems.ts","../src/isItemEvaluated.ts","../src/keywords/unevaluatedItems.ts","../src/draft2020.ts","../src/compileSchema.ts","../src/draftEditor.ts","../src/keywords/propertyDependencies.ts"],"sourcesContent":["import { isAnnotation } from \"../types\";\nimport { ValidationAnnotation, ValidationReturnType } from \"../Keyword\";\n\n/**\n * Flattens nested validation array results and filters items to only include errors, annotations and promises\n */\nexport default function sanitizeErrors(\n list: ValidationReturnType | ValidationReturnType[] | ValidationAnnotation[],\n result: ValidationAnnotation[] = []\n) {\n if (!Array.isArray(list)) {\n if (list !== undefined) {\n return [list];\n }\n return [];\n }\n for (const item of list) {\n if (Array.isArray(item)) {\n sanitizeErrors(item, result);\n } else if (isAnnotation(item) || item instanceof Promise) {\n result.push(item);\n }\n }\n return result;\n}\n","export default {\n DECLARATOR_ONEOF: \"oneOfProperty\",\n propertyBlacklist: [\"_id\"],\n DYNAMIC_PROPERTIES: [\n \"$ref\",\n \"$defs\",\n \"if\",\n \"then\",\n \"else\",\n \"allOf\",\n \"anyOf\",\n \"oneOf\",\n \"dependentSchemas\",\n \"dependentRequired\",\n \"definitions\",\n \"dependencies\",\n \"patternProperties\",\n \"propertyDependencies\"\n ],\n REGEX_FLAGS: \"u\",\n /** additional keywords that should not produce an unknown-keyword-warning */\n VALID_ANNOTATION_KEYWORDS: [\"$id\", \"$schema\", \"title\", \"description\", \"default\", \"oneOfProperty\"],\n /**\n * properties to keep from a $ref-schema when resolving a $ref (recursively)\n * this allows to overwrite specified properties locally on a $ref-definition\n *\n * - draft 2019-09\n * - draft 2020-12\n *\n * @example\n * {\n * title: \"custom component\",\n * $ref: \"#/$defs/component\",\n *\n * $defs: {\n * component: {\n * title: \"component\",\n * type: \"object\"\n * }\n * }\n * }\n * // results in\n * {\n * title: \"custom component\"\n * type: \"object\"\n * }\n */\n PROPERTIES_TO_MERGE: [\"title\", \"description\", \"default\", \"options\", \"x-options\", \"readOnly\", \"writeOnly\"]\n};\n","const toString = Object.prototype.toString;\n\nexport type JSType =\n\t| \"array\"\n\t| \"bigint\"\n\t| \"boolean\"\n\t| \"function\"\n\t| \"null\"\n\t| \"number\"\n\t| \"object\"\n\t| \"string\"\n\t| \"symbol\"\n\t| \"undefined\";\n\nexport function getTypeOf(value: unknown): JSType {\n\tconst type = toString.call(value).slice(8, -1).toLowerCase();\n\tif (type === \"file\") {\n\t\treturn \"object\";\n\t}\n\treturn type as JSType;\n}\n","import { getTypeOf } from \"./getTypeOf\";\n\nexport function isObject(v: unknown): v is Record<string, unknown> {\n return getTypeOf(v) === \"object\";\n}\n","import { getTypeOf } from \"../utils/getTypeOf\";\nimport { JsonSchema } from \"../types\";\nimport { isObject } from \"../utils/isObject\";\n\n/**\n * Create a simple json schema for the given input data\n * @param data - data to get json schema for\n */\nexport function createSchema(data: unknown): JsonSchema {\n // if (data === undefined) {\n // return undefined;\n // }\n\n const schema: JsonSchema =\n data === undefined\n ? {}\n : {\n type: getTypeOf(data)\n };\n\n if (schema.type === \"object\" && isObject(data)) {\n schema.properties = {};\n Object.keys(data).forEach((key) => (schema.properties[key] = createSchema(data[key])));\n }\n\n if (schema.type === \"array\" && Array.isArray(data)) {\n if (data.length === 1) {\n schema.items = createSchema(data[0]);\n } else {\n schema.items = data.map(createSchema);\n const sameTypes = schema.items.find((item: JsonSchema) => item.type !== schema.items[0].type) == null;\n if (sameTypes) {\n schema.items = schema.items[0];\n }\n }\n }\n\n return schema;\n}\n","/* eslint-disable @typescript-eslint/no-unused-expressions */\nimport { isSchemaNode, SchemaNode } from \"../types\";\n\nfunction eachProperty(nodeList: SchemaNode[], o?: Record<string, SchemaNode | unknown>) {\n if (o != null) {\n Object.values(o).forEach((node) => toSchemaNodes(node, nodeList));\n }\n}\n\nfunction eachItem(nodeList: SchemaNode[], a?: SchemaNode[]) {\n if (a != null) {\n a.forEach((node) => toSchemaNodes(node, nodeList));\n }\n}\n\nexport function toSchemaNodes(node: SchemaNode | unknown, nodeList: SchemaNode[] = []): SchemaNode[] {\n if (!isSchemaNode(node)) {\n return nodeList;\n }\n\n nodeList.push(node);\n\n eachProperty(nodeList, node.$defs);\n node.additionalProperties && toSchemaNodes(node.additionalProperties, nodeList);\n eachItem(nodeList, node.allOf);\n eachItem(nodeList, node.anyOf);\n node.contains && toSchemaNodes(node.contains, nodeList);\n eachProperty(nodeList, node.dependentSchemas);\n node.if && toSchemaNodes(node.if, nodeList);\n node.else && toSchemaNodes(node.else, nodeList);\n node.then && toSchemaNodes(node.then, nodeList);\n node.items && toSchemaNodes(node.items, nodeList);\n eachItem(nodeList, node.prefixItems);\n node.not && toSchemaNodes(node.not, nodeList);\n eachItem(nodeList, node.oneOf);\n node.patternProperties &&\n Object.values(node.patternProperties).forEach(({ node }) => toSchemaNodes(node, nodeList));\n eachProperty(nodeList, node.properties);\n node.propertyNames && toSchemaNodes(node.propertyNames, nodeList);\n node.unevaluatedProperties && toSchemaNodes(node.unevaluatedProperties, nodeList);\n node.unevaluatedItems && toSchemaNodes(node.unevaluatedItems, nodeList);\n\n return nodeList;\n}\n","import { resolve } from \"uri-js\";\n\nconst suffixes = /(#)+$/;\nconst trailingHash = /#$/;\nconst isDomain = /^[^:]+:\\/\\/[^/]+\\//;\nconst idAndPointer = /#.*$/;\n\n/**\n * Resolves a reference URI against a base URI.\n * Uses fast-uri (RFC 3986 compliant) for most cases, with special handling for JSON Schema specifics.\n *\n * This replaces the custom joinId logic while leveraging the standards-compliant fast-uri library.\n *\n * @param base - The base URI (e.g., current scope $id)\n * @param ref - The reference to resolve (e.g., $id, $ref, or json-pointer)\n * @returns The resolved absolute URI\n */\nexport function resolveUri(base?: string, ref?: string): string {\n if (ref == null) {\n return base?.replace(trailingHash, \"\") ?? \"#\";\n }\n\n if (base == null || base === \"#\") {\n return ref?.replace(trailingHash, \"\");\n }\n\n // If ref starts with #, it's a fragment - for JSON Schema, append to base without its fragment\n if (ref[0] === \"#\") {\n if (base[0] === \"/\") {\n return ref;\n }\n return `${base.replace(idAndPointer, \"\")}${ref.replace(suffixes, \"\")}`;\n }\n\n // If ref is a full domain, it's absolute\n if (isDomain.test(ref)) {\n return ref.replace(trailingHash, \"\");\n }\n\n return resolve(base, ref ?? \"\") ?? \"#\";\n}\n","import { JsonSchema } from \"../types\";\nimport { getTypeOf } from \"./getTypeOf\";\nimport { isObject } from \"./isObject\";\n\nexport function mergeSchema<T extends JsonSchema>(a: T, b: T, ...omit: string[]): T {\n if (b?.type === \"error\") {\n return b;\n } else if (a?.type === \"error\") {\n return a;\n }\n\n const aType = getTypeOf(a);\n const bType = getTypeOf(b);\n if (aType !== bType) {\n return a;\n }\n\n const schema = mergeSchema2(a, b) as T;\n for (const s of omit) {\n delete schema[s]; // eslint-disable-line @typescript-eslint/no-dynamic-delete\n }\n\n return schema;\n}\n\nexport function mergeSchema2(a: unknown, b: unknown, property?: string): unknown {\n if (isObject(a) && isObject(b)) {\n const newObject: Record<string, unknown> = {};\n [...Object.keys(a), ...Object.keys(b)]\n .filter((item, index, array) => array.indexOf(item) === index)\n .forEach((key) => (newObject[key] = mergeSchema2(a[key], b[key], key)));\n return newObject;\n }\n\n const aIsArray = Array.isArray(a);\n const bIsArray = Array.isArray(b);\n\n if (aIsArray && bIsArray) {\n if (property === \"required\" || property === \"anyOf\") {\n return a.concat(b).filter((item, index, array) => array.indexOf(item) === index);\n }\n if (property === \"items\" || property === \"prefixItems\") {\n const result: unknown[] = [];\n for (let i = 0; i < b.length; i += 1) {\n if (isObject(a[i]) && isObject(b[i]) && a[i].type === b[i].type) {\n result[i] = mergeSchema2(a[i], b[i]);\n } else {\n result.push(b[i] ?? a[i]);\n }\n }\n return result;\n }\n const result: unknown[] = [];\n const append: unknown[] = [];\n for (let i = 0; i < Math.max(a.length, b.length); i += 1) {\n if (isObject(a[i]) && isObject(b[i])) {\n result[i] = mergeSchema2(a[i], b[i]);\n } else {\n if (a[i] !== undefined && b[i] !== undefined) {\n result[i] = a[i];\n append.push(b[i]);\n } else if (a[i] !== undefined) {\n result[i] = a[i];\n } else if (b[i] !== undefined) {\n append.push(b[i]);\n }\n }\n }\n return [...result, ...append].filter((item, index, array) => array.indexOf(item) === index);\n }\n\n // mixed data-type for type keyword\n if (property === \"type\" && (aIsArray || bIsArray)) {\n // we merge to the specific type\n if (aIsArray && a.includes(b)) {\n return b;\n }\n if (bIsArray && b.includes(a)) {\n return a;\n }\n // extend the types if they do not match\n if (aIsArray) {\n return [...a, b];\n }\n if (bIsArray) {\n return [...b, a];\n }\n }\n\n if (bIsArray) {\n return b;\n }\n\n if (aIsArray) {\n return a;\n }\n\n if (b !== undefined) {\n return b;\n }\n\n return a;\n}\n","import { isSchemaNode, SchemaNode } from \"./types\";\nimport { mergeSchema } from \"./utils/mergeSchema\";\nimport { joinDynamicId } from \"./SchemaNode\";\n\ninterface SchemaNodeCB {\n toJSON?: () => string;\n order?: number;\n (...args: any[]): any; // eslint-disable-line @typescript-eslint/no-explicit-any\n}\n\nfunction sortCb(a: SchemaNodeCB, b: SchemaNodeCB) {\n return (b.order ?? 0) - (a.order ?? 0);\n}\n\nexport function removeDuplicates(fun: SchemaNodeCB, funIndex: number, list: SchemaNodeCB[]) {\n if (fun == null || list.indexOf(fun) !== funIndex) {\n return false;\n }\n const funName = fun.toJSON?.() ?? fun.name;\n return list.find((fun: SchemaNodeCB, index) => (fun.toJSON?.() ?? fun.name) === funName && index === funIndex);\n}\n\nfunction mergeObjects(a?: Record<string, SchemaNode>, b?: Record<string, SchemaNode>) {\n if (a == null || b == null) {\n return b || a;\n }\n const object: Record<string, SchemaNode> = {};\n [...Object.keys(a), ...Object.keys(b)]\n .filter((p, i, l) => l.indexOf(p) === i)\n .forEach((key) => {\n const result = mergeNode(a[key], b[key]);\n if (isSchemaNode(result)) {\n object[key] = result;\n }\n });\n\n return object;\n}\n\nfunction combineArrays<T>(a?: T[], b?: T[]): T[] | undefined {\n if (a == null || b == null) {\n return b || a;\n }\n return a.concat(b).filter((value, index, list) => list.indexOf(value) === index);\n}\n\nfunction mergePatternProperties(a?: SchemaNode[\"patternProperties\"], b?: SchemaNode[\"patternProperties\"]) {\n if (a == null || b == null) {\n return a || b;\n }\n const result = [...a];\n const pointerList = a.map((p) => p.node.evaluationPath);\n b.forEach((p) => {\n if (!pointerList.includes(p.node.evaluationPath)) {\n result.push(p);\n }\n });\n return result;\n}\n\nexport function mergeNode(a?: SchemaNode, b?: SchemaNode, ...omit: string[]): SchemaNode | undefined {\n if (a == null || b == null) {\n return a || b;\n }\n\n // do not merge items and prefixItems (+ additionalItems)\n const arraySelection: Partial<SchemaNode> = {};\n if ((a.items && b.prefixItems) || (a.prefixItems && b.items)) {\n if (b.prefixItems) {\n arraySelection.prefixItems = b.prefixItems;\n } else {\n arraySelection.items = b.items!;\n }\n } else {\n // prefixItems?: SchemaNode[];\n arraySelection.prefixItems = (b.prefixItems ?? a.prefixItems)!;\n arraySelection.items = mergeNode(a.items, b.items)!;\n }\n\n // we have no node-type if (atype !== b.type) {return a; }\n\n // @ts-expect-error simplified merging of objects\n const mergedNode: SchemaNode = {\n // note: {x: b.x ?? a.x} is already done by {...a, ...b}\n ...a,\n ...b,\n ...arraySelection,\n dynamicId: joinDynamicId(a.dynamicId, b.dynamicId),\n oneOfIndex: a.oneOfIndex ?? b.oneOfIndex,\n schema: mergeSchema(a.schema, b.schema, ...omit),\n parent: a.parent,\n resolvers: a.resolvers.concat(b.resolvers).filter(removeDuplicates).sort(sortCb),\n reducers: a.reducers.concat(b.reducers).filter(removeDuplicates).sort(sortCb),\n validators: a.validators.concat(b.validators).filter(removeDuplicates).sort(sortCb),\n\n additionalProperties: mergeNode(a.additionalProperties, b.additionalProperties),\n contains: mergeNode(a.contains, b.contains),\n enum: combineArrays(a.enum, b.enum),\n if: mergeNode(a.if, b.if),\n then: mergeNode(a.then, b.then),\n else: mergeNode(a.else, b.else),\n not: mergeNode(a.not, b.not),\n propertyNames: mergeNode(a.propertyNames, b.propertyNames),\n unevaluatedProperties: mergeNode(a.unevaluatedProperties, b.unevaluatedProperties),\n unevaluatedItems: mergeNode(a.unevaluatedItems, b.unevaluatedItems),\n $defs: mergeObjects(a.$defs, b.$defs),\n patternProperties: mergePatternProperties(a.patternProperties, b.patternProperties),\n properties: mergeObjects(a.properties, b.properties),\n required: combineArrays(a.required, b.required)\n };\n\n // this removes any function that has no keyword associated on schema\n function filterKeywordsBySchema(fun: SchemaNodeCB) {\n const funName = fun.toJSON?.() ?? fun.name;\n if (mergedNode.schema?.[funName] === undefined) {\n // @ts-expect-error forced key\n mergedNode[funName] = undefined;\n return false;\n }\n return true;\n }\n\n // @ts-expect-error forced key\n omit?.forEach((key) => (mergedNode[key] = undefined));\n // @todo better run addX features to determine removal as it is more performant and direct?\n mergedNode.resolvers = mergedNode.resolvers.filter(filterKeywordsBySchema);\n mergedNode.reducers = mergedNode.reducers.filter(filterKeywordsBySchema);\n mergedNode.validators = mergedNode.validators.filter(filterKeywordsBySchema);\n\n return mergedNode;\n}\n","/**\n * Omit properties from input schema. Accepts any number of properties to\n * remove. Example:\n *\n * ```ts\n * omit(myObject, \"if\", \"dependencies\");\n * ```\n *\n * @returns shallow copy of input object without specified properties\n */\nexport function omit(object: Record<string, unknown>, ...keysToOmit: string[]) {\n const result: Record<string, unknown> = {};\n Object.keys(object).forEach((key) => {\n if (!keysToOmit.includes(key)) {\n result[key] = object[key];\n }\n });\n return result;\n}\n","import { isObject } from \"../utils/isObject\";\n\nexport function pick<T extends { [P in keyof T]: unknown }, K extends keyof T>(value: T, ...properties: K[]) {\n if (!isObject(value) || properties.length === 0) {\n return value;\n }\n const result = {} as Pick<T, K>;\n properties.forEach((property) => {\n if (value[property] !== undefined) {\n result[property] = value[property];\n }\n });\n return result;\n}\n","export function render(template: string, data: Record<string, unknown> = {}): string {\n return template.replace(/\\{\\{(\\w+)\\}\\}/g, (_, key) => {\n const variable = data[key];\n if (variable === null || variable === undefined) return \"\"; // optional\n if (typeof variable === \"object\") {\n return JSON.stringify(variable);\n }\n return String(variable);\n });\n}\n","import { BooleanSchema, isJsonError, JsonSchema, SchemaNode } from \"./types\";\nimport { SchemaNodeWithRequired, ValidationPath, ValidationReturnType } from \"./Keyword\";\nimport sanitizeErrors from \"./utils/sanitizeErrors\";\n\nexport function validateNode(node: SchemaNode, data: unknown, pointer: string, path: ValidationPath) {\n if (isJsonError(node)) {\n return [node];\n }\n path.push({ pointer, node });\n const schema = node.schema as BooleanSchema | JsonSchema;\n if (schema === true) {\n return [];\n }\n if (schema === false) {\n return [\n node.createError(\"invalid-data-error\", {\n value: data,\n pointer,\n schema: node.schema\n })\n ];\n }\n const errors: ValidationReturnType = [];\n for (const validate of node.validators) {\n const result = validate({ node: node as SchemaNodeWithRequired<keyof SchemaNode>, data, pointer, path });\n if (Array.isArray(result)) {\n errors.push(...result);\n } else if (result) {\n errors.push(result);\n }\n }\n return sanitizeErrors(errors);\n}\n","const hasOwnProperty = Object.prototype.hasOwnProperty;\nexport const hasProperty = (value: Record<string, unknown>, property: string) =>\n !(value[property] === undefined || !hasOwnProperty.call(value, property));\n","import { isObject } from \"../utils/isObject\";\n\nexport function getValue(data: unknown, key: string | number) {\n if (isObject(data)) {\n return data[key];\n } else if (Array.isArray(data)) {\n return data[key as number];\n }\n}\n","import { GetNodeOptions, isSchemaNode, SchemaNode } from \"./SchemaNode\";\nimport { isJsonError, NodeOrError, OptionalNodeOrError } from \"./types\";\nimport { split } from \"@sagold/json-pointer\";\nimport { getValue } from \"./utils/getValue\";\n\n// prettier-ignore\nexport function getNode(pointer: string, data: unknown, options: { withSchemaWarning: true } & GetNodeOptions): NodeOrError;\nexport function getNode(pointer: string, data: unknown, options: { createSchema: true } & GetNodeOptions): NodeOrError;\nexport function getNode(pointer: string, data?: unknown, options?: GetNodeOptions): OptionalNodeOrError;\n\n/**\n * Returns a node containing JSON Schema of a data JSON Pointer.\n *\n * - the returned node will have a reduced schema based on given input data\n * - the returned node $ref is resolved\n *\n * To resolve dynamic schema where the type of JSON Schema is evaluated by\n * its value, a data object has to be passed in options.\n *\n * Per default this function will return `undefined` schema for valid properties\n * that do not have a defined schema. Use the option `withSchemaWarning: true` to\n * receive an error with `code: schema-warning` containing the location of its\n * last evaluated json-schema.\n *\n * @returns { node } or { error } where node can also be undefined (valid but undefined)\n */\nexport function getNode(\n pointer: string,\n data?: unknown,\n options: GetNodeOptions = {}\n): OptionalNodeOrError | NodeOrError {\n options.path = options.path ?? [];\n options.withSchemaWarning = options.withSchemaWarning ?? false;\n options.pointer = options.pointer ?? \"#\";\n // @ts-expect-error explicitely any\n const node = this as SchemaNode;\n const keys = split(pointer);\n if (keys.length === 0) {\n const result = node.resolveRef(options);\n return isJsonError(result) ? { node: undefined, error: result } : { node: result, error: undefined };\n }\n let currentPointer = \"#\";\n let currentNode = node;\n for (let i = 0, l = keys.length; i < l; i += 1) {\n currentPointer = `${currentPointer}/${keys[i]}`;\n const result = currentNode.getNodeChild(keys[i], data, { ...options, pointer: currentPointer });\n if (result.error) {\n return result;\n }\n if (result.node == null) {\n return result;\n }\n currentNode = result.node;\n data = getValue(data, keys[i]);\n }\n\n const { node: reducedNode, error: reduceError } = currentNode.resolveRef(options).reduceNode(data);\n\n if (isJsonError(reduceError)) {\n return { node: undefined, error: reduceError };\n }\n if (isSchemaNode(reducedNode)) {\n return { node: reducedNode, error: undefined };\n }\n\n return { error: undefined };\n}\n","import { GetNodeOptions, isSchemaNode, SchemaNode } from \"./SchemaNode\";\nimport { isJsonError, NodeOrError, OptionalNodeOrError } from \"./types\";\nimport { getValue } from \"./utils/getValue\";\n\nexport function getNodeChild(key: string | number, data: unknown, options: { withSchemaWarning: true } & GetNodeOptions): NodeOrError; // prettier-ignore\nexport function getNodeChild(key: string | number, data: unknown, options: { createSchema: true } & GetNodeOptions): NodeOrError; // prettier-ignore\nexport function getNodeChild(key: string | number, data?: unknown, options?: GetNodeOptions): OptionalNodeOrError;\n\n/**\n * Returns the child for the given property-name or array-index\n *\n * - the returned child node is **not reduced**\n * - a child node $ref is resolved\n *\n * @returns { node } or { error } where node can also be undefined (valid but undefined)\n */\nexport function getNodeChild(\n key: string | number,\n data?: unknown,\n options: GetNodeOptions = {}\n): OptionalNodeOrError | NodeOrError {\n options.path = options.path ?? [];\n options.withSchemaWarning = options.withSchemaWarning ?? false;\n options.pointer = options.pointer ?? \"#\";\n const { path, pointer } = options;\n\n // reduce parent\n // @ts-expect-error implicitely any\n let parentNode = this as SchemaNode;\n if (parentNode.reducers.length) {\n const result = parentNode.reduceNode(data, { key, path, pointer });\n if (result.error) {\n return result;\n }\n if (isSchemaNode(result.node)) {\n parentNode = result.node;\n }\n }\n\n // find child node\n for (const resolver of parentNode.resolvers) {\n const schemaNode = resolver({ data, key, node: parentNode });\n // a matching resolver found an error, return\n if (isJsonError(schemaNode)) {\n return { node: undefined, error: schemaNode };\n }\n // a matching resolver found a child node, return\n if (isSchemaNode(schemaNode)) {\n return { node: schemaNode.resolveRef({ pointer, path }), error: undefined };\n }\n }\n\n // no child node was found, but the child node is valid\n if (options.createSchema === true) {\n const newNode = parentNode.compileSchema(\n parentNode.createSchema(getValue(data, key)),\n `${parentNode.evaluationPath}/additional`,\n `${parentNode.schemaLocation}/additional`\n );\n return { node: newNode, error: undefined };\n }\n\n if (options.withSchemaWarning === true) {\n const error = parentNode.createError(\"schema-warning\", {\n pointer,\n value: data,\n schema: parentNode.schema,\n key\n });\n return { node: undefined, error };\n }\n\n return { node: undefined };\n}\n","import { copy } from \"fast-copy\";\nimport sanitizeErrors from \"./utils/sanitizeErrors\";\nimport settings from \"./settings\";\nimport type {\n JsonSchemaReducer,\n JsonSchemaResolver,\n JsonSchemaValidator,\n Keyword,\n Maybe,\n ValidationAnnotation,\n ValidationPath\n} from \"./Keyword\";\nimport { createSchema } from \"./methods/createSchema\";\nimport { Draft } from \"./Draft\";\nimport { toSchemaNodes } from \"./methods/toSchemaNodes\";\nimport {\n isJsonError,\n isJsonSchema,\n JsonSchema,\n BooleanSchema,\n JsonError,\n AnnotationData,\n DefaultErrors,\n OptionalNodeOrError,\n NodeOrError,\n JsonAnnotation,\n isJsonAnnotation,\n isBooleanSchema\n} from \"./types\";\nimport { isObject } from \"./utils/isObject\";\nimport { join } from \"@sagold/json-pointer\";\nimport { resolveUri } from \"./utils/resolveUri\";\nimport { mergeNode } from \"./mergeNode\";\nimport { omit } from \"./utils/omit\";\nimport { pick } from \"./utils/pick\";\nimport { render } from \"./errors/render\";\nimport { TemplateOptions } from \"./methods/getData\";\nimport { validateNode } from \"./validateNode\";\nimport { hasProperty } from \"./utils/hasProperty\";\nimport { getNode } from \"./getNode\";\nimport { getNodeChild } from \"./getNodeChild\";\nimport { DataNode } from \"./methods/toDataNodes\";\n\nconst { DYNAMIC_PROPERTIES, REGEX_FLAGS, DECLARATOR_ONEOF, VALID_ANNOTATION_KEYWORDS } = settings;\n\nexport function isSchemaNode(value: unknown): value is SchemaNode {\n return isObject(value) && Array.isArray(value?.reducers) && Array.isArray(value?.resolvers);\n}\n\nexport function isReduceable(node: SchemaNode) {\n for (let i = 0, l = DYNAMIC_PROPERTIES.length; i < l; i += 1) {\n // @ts-expect-error interface to object conversion\n if (hasProperty(node, DYNAMIC_PROPERTIES[i])) {\n return true;\n }\n }\n return false;\n}\n\nfunction getDraft(drafts: Draft[], $schema: string) {\n if (!Array.isArray(drafts) || drafts.length === 0) {\n throw new Error(`Missing drafts in 'compileSchema({ $schema: \"${$schema}\" })'`);\n }\n if (drafts.length === 1) {\n return drafts[0];\n }\n return drafts.find((d) => new RegExp(d.$schemaRegEx, REGEX_FLAGS).test($schema)) ?? drafts[drafts.length - 1];\n}\n\nexport type Context = {\n /** root node of this JSON Schema */\n rootNode: SchemaNode;\n /** Fallback _draft_ version in case no _draft_ is specified by `schema.$schema` */\n draft?: string;\n /** available draft configurations */\n drafts: Draft[];\n /** [SHARED ACROSS REMOTES] root nodes of registered remote JSON Schema, stored by id/url */\n remotes: Record<string, SchemaNode>;\n /** references stored by fully resolved schema-$id + local-pointer */\n refs: Record<string, SchemaNode>;\n /** anchors stored by fully resolved schema-$id + $anchor */\n anchors: Record<string, SchemaNode>;\n /** [SHARED ACROSS REMOTES] dynamicAnchors stored by fully resolved schema-$id + $anchor */\n dynamicAnchors: Record<string, SchemaNode>;\n /** JSON Schema parser, validator, reducer and resolver for this JSON Schema (root schema and its child nodes) */\n keywords: Draft[\"keywords\"];\n /** JSON Schema draft dependend methods */\n methods: Draft[\"methods\"];\n /** draft version */\n version: Draft[\"version\"];\n /** draft errors & template-strings */\n errors: Draft[\"errors\"];\n /** draft formats & validators */\n formats: Draft[\"formats\"];\n /** [SHARED USING ADD REMOTE] getData default options */\n getDataDefaultOptions?: TemplateOptions;\n /** [SHARED USING ADD REMOTE] collect unknown keywords in schemaAnnotations */\n withSchemaAnnotations?: boolean;\n /** [SHARED USING ADD REMOTE] throw error on validation when ref cannot be resolved */\n throwOnInvalidRef?: boolean;\n};\n\nexport interface SchemaNode extends SchemaNodeMethodsType {\n /** shared context across nodes of JSON schema and shared properties across all remotes */\n context: Context;\n /** JSON Schema of node */\n schema: JsonSchema;\n /**\n * Evaluation Path - The location of the keyword that produced the annotation or error.\n * The purpose of this data is to show the resolution path which resulted in the subschema\n * that contains the keyword.\n *\n * - relative to the root of the principal schema; should include (inline) any $ref segments in the path\n * - JSON pointer\n */\n evaluationPath: string;\n /**\n * Schema Location - The direct location to the keyword that produced the annotation\n * or error. This is provided as a convenience to the user so that they don't have to resolve\n * the keyword's subschema, which may not be trivial task. It is only provided if the relative\n * location contains $refs (otherwise, the two locations will be the same).\n *\n * - absolute URI\n * - may not have any association to the principal schema\n */\n schemaLocation: string;\n /** id created when combining subschemas */\n dynamicId: string;\n /** reference to parent node (node used to compile this node) */\n parent?: SchemaNode | undefined;\n /** JSON Pointer from last $id ~~to this location~~ to resolve $refs to $id#/idLocalPointer */\n lastIdPointer: string;\n /** when reduced schema containing `oneOf` schema, `oneOfIndex` stores `oneOf`-item used for merge */\n oneOfIndex?: number;\n\n reducers: JsonSchemaReducer[];\n resolvers: JsonSchemaResolver[];\n validators: JsonSchemaValidator[];\n schemaValidation?: ValidationAnnotation[];\n\n // parsed schema properties (registered by parsers)\n $id?: string;\n $defs?: Record<string, SchemaNode>;\n $ref?: string;\n additionalProperties?: SchemaNode;\n allOf?: SchemaNode[];\n anyOf?: SchemaNode[];\n contains?: SchemaNode;\n dependentRequired?: Record<string, string[]>;\n dependentSchemas?: Record<string, SchemaNode | boolean>;\n deprecated?: boolean;\n else?: SchemaNode;\n enum?: unknown[];\n if?: SchemaNode;\n /**\n * # Items-array schema - for all drafts\n *\n * - for drafts prior 2020-12 `schema.items[]`-schema stored as `node.prefixItems`\n *\n * Validation succeeds if each element of the instance validates against the schema at the\n * same position, if any.\n *\n * The `prefixItems` keyword restricts a number of items from the start of an array instance\n * to validate against the given sequence of subschemas, where the item at a given index in\n * the array instance is evaluated against the subschema at the given index in the `prefixItems`\n * array, if any. Array items outside the range described by the `prefixItems` keyword is\n * evaluated against the items keyword, if present.\n *\n * [Docs](https://www.learnjsonschema.com/2020-12/applicator/prefixitems/)\n * | [Examples](https://json-schema.org/understanding-json-schema/reference/array#tupleValidation)\n */\n prefixItems?: SchemaNode[];\n /**\n * # Items-object schema for additional array item - for all drafts\n *\n * - for drafts prior 2020-12 `schema.additionalItems` object-schema stored as `node.items`\n *\n * Validation succeeds if each element of the instance not covered by `prefixItems` validates\n * against this schema.\n *\n * The items keyword restricts array instance items not described by the sibling `prefixItems`\n * keyword (if any), to validate against the given subschema. Whetherthis keyword was evaluated\n * against any item of the array instance is reported using annotations.\n *\n * [Docs](https://www.learnjsonschema.com/2020-12/applicator/items/)\n * | [Examples](https://json-schema.org/understanding-json-schema/reference/array#items)\n * | [AdditionalItems Specification](https://json-schema.org/draft/2019-09/draft-handrews-json-schema-02#additionalItems)\n */\n items?: SchemaNode;\n maximum?: number;\n minimum?: number;\n maxItems?: number;\n maxLength?: number;\n maxProperties?: number;\n minItems?: number;\n minLength?: number;\n minProperties?: number;\n not?: SchemaNode;\n oneOf?: SchemaNode[];\n multipleOf?: number;\n pattern?: RegExp;\n patternProperties?: { name: string; pattern: RegExp; node: SchemaNode }[];\n propertyDependencies?: Record<string, Record<string, SchemaNode>>;\n properties?: Record<string, SchemaNode>;\n propertyNames?: SchemaNode;\n required?: string[];\n then?: SchemaNode;\n type?: string | string[];\n unevaluatedItems?: SchemaNode;\n unevaluatedProperties?: SchemaNode;\n uniqueItems?: true;\n}\n\n/**\n * Fixed SchemaNode mixin methods\n */\ninterface SchemaNodeMethodsType {\n compileSchema(\n schema: JsonSchema | BooleanSchema,\n evaluationPath?: string,\n schemaLocation?: string,\n dynamicId?: string\n ): SchemaNode;\n createError<T extends string = DefaultErrors>(code: T, data: AnnotationData, message?: string): JsonError;\n createAnnotation<T extends string = DefaultErrors>(code: T, data: AnnotationData, message?: string): JsonAnnotation;\n createSchema(data?: unknown): JsonSchema;\n\n /**\n * Returns a node matching the given location (pointer) in data\n *\n * - the returned node will have a **reduced schema** based on given input data\n * - return returned node $ref is resolved\n *\n * To resolve dynamic schema where the type of JSON Schema is evaluated by\n * its value, a data object has to be passed in options.\n *\n * Per default this function will return `undefined` schema for valid properties\n * that do not have a defined schema. Use the option `withSchemaWarning: true` to\n * receive an error with `code: schema-warning` containing the location of its\n * last evaluated json-schema.\n *\n * @returns { node } or { error } where node can also be undefined (valid but undefined)\n */\n getNode(pointer: string, data: unknown, options: { withSchemaWarning: true } & GetNodeOptions): NodeOrError;\n getNode(pointer: string, data: unknown, options: { createSchema: true } & GetNodeOptions): NodeOrError;\n getNode(pointer: string, data?: unknown, options?: GetNodeOptions): OptionalNodeOrError;\n\n /**\n * Returns the child for the given property-name or array-index\n *\n * - the returned child node is **not reduced**\n * - a child node $ref is resolved\n *\n * @returns { node } or { error } where node can also be undefined (valid but undefined)\n */\n getNodeChild(\n key: string | number,\n data: unknown,\n options: { withSchemaWarning: true } & GetNodeOptions\n ): NodeOrError;\n getNodeChild(key: string | number, data: unknown, options: { createSchema: true } & GetNodeOptions): NodeOrError;\n getNodeChild(key: string | number, data?: unknown, options?: GetNodeOptions): OptionalNodeOrError;\n\n getChildSelection(property: string | number): JsonError | SchemaNode[];\n getNodeRef($ref: string): SchemaNode | undefined;\n getNodeRoot(): SchemaNode;\n getDraftVersion(): string;\n getData(data?: unknown, options?: TemplateOptions): any; // eslint-disable-line @typescript-eslint/no-explicit-any\n reduceNode(\n data: unknown,\n options?: { key?: string | number; pointer?: string; path?: ValidationPath }\n ): OptionalNodeOrError;\n resolveRef: (args?: { pointer?: string; path?: ValidationPath }) => SchemaNode;\n validate(data: unknown, pointer?: string, path?: ValidationPath): ValidateReturnType;\n addRemoteSchema(url: string, schema: JsonSchema | BooleanSchema): SchemaNode;\n toSchemaNodes(): SchemaNode[];\n toDataNodes(data: unknown, pointer?: string): DataNode[];\n toJSON(): unknown;\n}\n\nexport type GetNodeOptions = {\n /**\n * Per default `undefined` is returned for valid data, but undefined schema.\n *\n * - Using `withSchemaWarning:true` will return an error instead: `{ type: \"error\", code: \"schema-warning\" }`\n */\n withSchemaWarning?: boolean;\n /**\n * Per default `undefined` is returned for valid data, but undefined schema.\n *\n * - Using `createSchema:true` will create a schema instead\n */\n createSchema?: boolean;\n path?: ValidationPath;\n pointer?: string;\n};\n\nexport type ValidateReturnType = {\n /**\n * True, if data is valid to the compiled schema.\n * Does not include async errors.\n */\n valid: boolean;\n /**\n * List of validation errors or empty\n */\n errors: JsonError[];\n /**\n * List of annotations from validators\n */\n annotations: JsonAnnotation[];\n /**\n * List of Promises resolving to `JsonError|undefined` or empty.\n */\n errorsAsync: Promise<Maybe<ValidationAnnotation>[]>[];\n};\n\nexport function joinDynamicId(a?: string, b?: string) {\n if (a == b) {\n return a ?? \"\";\n }\n if (a == null || b == null) {\n return (a || b) ?? \"\";\n }\n if (a.startsWith(b)) {\n return a;\n }\n if (b.startsWith(a)) {\n return b;\n }\n return `${a}+${b}`;\n}\n\nexport const SchemaNodeMethods = {\n /**\n * Compiles a child-schema of this node to its context\n * @returns SchemaNode representing the passed JSON Schema\n */\n compileSchema(schema: JsonSchema, evaluationPath: string, schemaLocation?: string, dynamicId?: string): SchemaNode {\n const parentNode = this as SchemaNode;\n evaluationPath = evaluationPath ?? parentNode.evaluationPath;\n const nextFragment = evaluationPath.split(\"/$ref\")[0];\n const node: SchemaNode = {\n lastIdPointer: parentNode.lastIdPointer, // ref helper\n context: parentNode.context,\n parent: parentNode,\n evaluationPath,\n dynamicId: joinDynamicId(parentNode.dynamicId, dynamicId),\n schemaLocation: schemaLocation ?? join(parentNode.schemaLocation, nextFragment),\n reducers: [],\n resolvers: [],\n validators: [],\n schema,\n ...SchemaNodeMethods\n };\n\n if (!isJsonSchema(schema) && !isBooleanSchema(schema)) {\n node.schemaValidation = [\n node.createError(\"schema-error\", {\n pointer: schemaLocation ?? evaluationPath,\n schema,\n value: undefined,\n message: `JSON schema must be object or boolean - reveived: '${schema}'`\n })\n ];\n return node;\n }\n const schemaValidation = addKeywords(node).filter((err) => err != null);\n node.schemaValidation = sanitizeErrors(schemaValidation);\n\n return node;\n },\n\n createError<T extends string = DefaultErrors>(code: T, data: AnnotationData, message?: string): JsonError {\n const node = this as SchemaNode;\n let errorMessage = message;\n if (errorMessage === undefined) {\n const error = node.schema?.errorMessages?.[code] ?? node.context.errors[code];\n if (typeof error === \"function\") {\n return error(data);\n }\n errorMessage = render(error ?? name, data);\n }\n return { type: \"error\", code, message: errorMessage, data };\n },\n\n createAnnotation<T extends string = DefaultErrors>(\n code: T,\n data: AnnotationData,\n message?: string\n ): JsonAnnotation {\n const node = this as SchemaNode;\n let annotationMessage = message;\n if (annotationMessage === undefined) {\n const error = node.schema?.errorMessages?.[code] ?? node.context.errors[code];\n if (typeof error === \"function\") {\n return error(data);\n }\n annotationMessage = render(error ?? name, data);\n }\n return { type: \"annotation\", code, message: annotationMessage, data };\n },\n\n createSchema,\n\n getChildSelection(property: string | number): JsonError | SchemaNode[] {\n const node = this as SchemaNode;\n return node.context.methods.getChildSelection(node, property);\n },\n\n getNode,\n getNodeChild,\n\n /**\n * @returns for $ref, the corresponding SchemaNode or undefined\n */\n getNodeRef($ref: string): SchemaNode | undefined {\n const node = this as SchemaNode;\n return node.compileSchema({ $ref }, \"$dynamic\").resolveRef();\n },\n\n getNodeRoot() {\n const node = this as SchemaNode;\n return node.context.rootNode;\n },\n\n /**\n * @returns draft version this JSON Schema is evaluated by\n */\n getDraftVersion() {\n return (this as SchemaNode).context.version;\n },\n\n /**\n * @returns data that is valid to the schema of this node\n */\n getData(data?: unknown, options?: TemplateOptions) {\n const node = this as SchemaNode;\n const opts = {\n recursionLimit: 1,\n ...node.context.getDataDefaultOptions,\n cache: {},\n ...(options ?? {})\n };\n return node.context.methods.getData(node, data, opts);\n },\n\n /**\n * @returns SchemaNode with a reduced JSON Schema matching the given data\n */\n reduceNode(\n data: unknown,\n options: { key?: string | number; pointer?: string; path?: ValidationPath } = {}\n ): OptionalNodeOrError {\n const node = this as SchemaNode;\n const { key = \"missing-key\", pointer = node.evaluationPath, path } = options;\n\n // @ts-expect-error bool schema\n if (node.schema === false) {\n return { node, error: undefined };\n // @ts-expect-error bool schema\n } else if (node.schema === true) {\n const nextNode = node.compileSchema(createSchema(data), node.evaluationPath, node.schemaLocation);\n path?.push({ pointer, node });\n return { node: nextNode, error: undefined };\n }\n\n let schema;\n // we need to copy node to prevent modification of source\n // @todo does mergeNode break immutability?\n let workingNode = node.compileSchema(node.schema, node.evaluationPath, node.schemaLocation);\n const reducers = node.reducers;\n for (const reducer of reducers) {\n const result = reducer({ data, key, node, pointer, path: path ?? [] });\n if (isJsonError(result)) {\n return { node: undefined, error: result };\n }\n if (result) {\n // @ts-expect-error bool schema - for undefined & false schema return false schema\n if (result.schema === false) {\n schema = false;\n break;\n }\n // compilation result for data of current schemain order to merge results, we rebuild\n // node from schema alternatively we would need to merge by node-property\n workingNode = mergeNode(workingNode, result) as SchemaNode;\n }\n }\n\n if (schema === false) {\n // @ts-expect-error bool schema\n return { node: { ...node, schema: false, reducers: [] } as SchemaNode, error: undefined };\n }\n\n if (workingNode !== node) {\n path?.push({ pointer, node });\n }\n\n // remove dynamic properties of node\n workingNode.schema = omit(workingNode.schema, DECLARATOR_ONEOF, ...DYNAMIC_PROPERTIES);\n // @ts-expect-error string accessing schema props\n DYNAMIC_PROPERTIES.forEach((prop) => (workingNode[prop] = undefined));\n return { node: workingNode, error: undefined };\n },\n\n /**\n * @returns validation result of data validated by this node's JSON Schema\n */\n validate(data: unknown, pointer = \"#\", path: ValidationPath = []) {\n const node = this as SchemaNode;\n const errors = validateNode(node, data, pointer, path) ?? [];\n const syncErrors: JsonError[] = [];\n const annotations: JsonAnnotation[] = [];\n const flatErrorList = sanitizeErrors(Array.isArray(errors) ? errors : [errors]).filter(isJsonError);\n\n const errorsAsync: Promise<Maybe<ValidationAnnotation>[]>[] = [];\n sanitizeErrors(Array.isArray(errors) ? errors : [errors]).forEach((error) => {\n if (isJsonError(error)) {\n if (node.context.throwOnInvalidRef && error.code === \"ref-error\") {\n const refError = new Error(\"Invalid $ref: \" + error.message);\n // @ts-expect-error unknown error-property\n refError.data = syncErrors;\n throw refError;\n }\n\n syncErrors.push(error);\n } else if (error instanceof Promise) {\n errorsAsync.push(error.then(sanitizeErrors));\n } else if (isJsonAnnotation(err