From db80a491fea2c3a28427b063f0b1e2958661effb Mon Sep 17 00:00:00 2001 From: Sascha Goldhofer Date: Sun, 14 Apr 2024 00:49:24 +0200 Subject: [PATCH] chore: cleanup --- README.md | 15 ++++--- index.ts | 6 +-- lib/compileSchema.ts | 2 - lib/draft/index.ts | 13 ++++-- lib/draft04/index.ts | 20 ++++----- lib/draft06/index.ts | 20 ++++----- lib/draft06/validation/type.ts | 47 ---------------------- lib/draft07/index.ts | 20 ++++----- lib/draft2019/index.ts | 22 +++++----- lib/draft2019/validation/keyword.ts | 1 - lib/each.ts | 2 +- lib/features/allOf.ts | 8 ++-- lib/features/anyOf.ts | 6 +-- lib/features/dependencies.ts | 6 +-- lib/getChildSchemaSelection.ts | 5 +-- lib/getSchema.ts | 16 +++----- lib/getTemplate.ts | 14 +++---- lib/jsoneditor/index.ts | 2 +- lib/reduceSchema.ts | 5 +-- lib/resolveDynamicSchema.ts | 2 +- lib/resolveRef.strict.ts | 3 -- lib/{resolveRef.merge.ts => resolveRef.ts} | 13 ++---- lib/schemaNode.ts | 35 ++++++++++------ lib/utils/shallowCloneSchema.ts | 3 -- lib/validate.ts | 4 +- 25 files changed, 118 insertions(+), 172 deletions(-) delete mode 100644 lib/compileSchema.ts delete mode 100644 lib/draft06/validation/type.ts rename lib/{resolveRef.merge.ts => resolveRef.ts} (83%) diff --git a/README.md b/README.md index 478f8d86..3630d09f 100644 --- a/README.md +++ b/README.md @@ -799,21 +799,20 @@ A _resolver_ is a simple method implementing a specific feature of json-schema t #### `resolveRef` with merge -The default json-schema behaviour for `$ref` resolution is to replace the schema where a `$ref` is defined. In some scenarios you what to add context-specific information (e.g., a specific _title_). For this, a modified `$ref`-resolver is exposed by `json-schema-library`: +Until _draft07_ json-schema behaviour for `$ref` resolution is to replace the schema where a `$ref` is defined. Since _draft2019-09_ $ref resolution merges the resolved schema, which can be used to add context-specific information (e.g., a specific _title_). +To add this behaviour to older drafts, a `$ref`-resolver is exposed by `json-schema-library`: ```ts -import { Draft2019, resolveRefMerge } from "json-schema-library"; -const jsonSchema = new Draft2019(mySchema, { resolveRef: resolveRefMerge }); +import { Draft2019, resolveRef } from "json-schema-library"; +const jsonSchema = new Draft2019(mySchema, { resolveRef }); ``` -`resolveRefMerge` performs a shallow merge (first level of properties), adding the local schemas properties last. - -**Caution:** With this resolver, it is possible to overwrite json-schema behavioural properties. Treat with care. +`resolveRef` performs a shallow merge (first level of properties), adding the local schemas properties last. The ref-resolver for draft07 and below is exported as `resolveRefStrict`.
Example ```ts -import { Draft2019, resolveRefMerge } from "json-schema-library"; +import { Draft07, resolveRef } from "json-schema-library"; const mySchema = { type: "object", properties: { @@ -830,7 +829,7 @@ const mySchema = { } }; -const jsonSchema = new Draft2019(mySchema, { resolveRef: resolveRefMerge }); +const jsonSchema = new Draft07(mySchema, { resolveRef }); const subHeaderSchema = jsonSchema.getSchema("#/subHeader"); expect(subHeaderSchema).to.eq({ diff --git a/index.ts b/index.ts index 2e7588ad..960ad039 100644 --- a/index.ts +++ b/index.ts @@ -2,8 +2,8 @@ import { createError, createCustomError } from "./lib/utils/createCustomError"; import getTypeOf from "./lib/getTypeOf"; import { resolveOneOf, resolveOneOfFuzzy } from "./lib/features/oneOf"; import { resolveAllOf } from "./lib/features/allOf"; -import resolveRef from "./lib/resolveRef.strict"; -import resolveRefMerge from "./lib/resolveRef.merge"; +import resolveRefStrict from "./lib/resolveRef.strict"; +import resolveRef from "./lib/resolveRef"; import settings from "./lib/config/settings"; import strings from "./lib/config/strings"; import validateAsync from "./lib/validateAsync"; @@ -49,8 +49,8 @@ export { resolveDynamicSchema, // v8 resolveOneOf, resolveOneOfFuzzy, + resolveRefStrict, resolveRef, - resolveRefMerge, settings, validateAsync // async validation of data by a schema }; diff --git a/lib/compileSchema.ts b/lib/compileSchema.ts deleted file mode 100644 index 72c32675..00000000 --- a/lib/compileSchema.ts +++ /dev/null @@ -1,2 +0,0 @@ -import compile from "./compile"; -export default compile; diff --git a/lib/draft/index.ts b/lib/draft/index.ts index 7b75074d..bd1158b1 100644 --- a/lib/draft/index.ts +++ b/lib/draft/index.ts @@ -1,5 +1,5 @@ import addRemoteSchema from "../addRemoteSchema"; -import compileSchema from "../compileSchema"; +import compileSchema from "../compile"; import copy from "../utils/copy"; import createSchemaOf from "../createSchemaOf"; import getChildSchemaSelection from "../getChildSchemaSelection"; @@ -32,6 +32,7 @@ export type DraftConfig = { validateFormat: Record; templateDefaultOptions?: TemplateOptions; + createNode: typeof createNode; addRemoteSchema: typeof addRemoteSchema; compileSchema: typeof compileSchema; createSchemaOf: typeof createSchemaOf; @@ -113,7 +114,7 @@ export class Draft { * @param [pointer] - pointer to current data. Default to rootPointer */ each(data: any, callback: EachCallback, schema?: JsonSchema, pointer?: JsonPointer) { - const node = createNode(this, schema ?? this.rootSchema, pointer); + const node = this.createNode(schema ?? this.rootSchema, pointer); return this.config.each(node, data, callback); } @@ -178,6 +179,10 @@ export class Draft { return this.config.isValid(this, data, schema, pointer); } + createNode(schema: JsonSchema, pointer = "#") { + return this.config.createNode(this, schema, pointer); + } + resolveAnyOf(node: SchemaNode, data: unknown): SchemaNode | JsonError { return this.config.resolveAnyOf(node, data); } @@ -217,7 +222,7 @@ export class Draft { if (isSchemaNode(key)) { return this.config.step(key, schema, data); } - const node = createNode(this, schema ?? this.rootSchema, pointer); + const node = this.createNode(schema ?? this.rootSchema, pointer); return this.config.step(node, key, data); } @@ -238,7 +243,7 @@ export class Draft { return this.config.validate(inuptNode, inputData); } - const node = createNode(this, schema, pointer); + const node = this.createNode(schema, pointer); return this.config.validate(node, data); } } diff --git a/lib/draft04/index.ts b/lib/draft04/index.ts index 2ea33649..403e976e 100644 --- a/lib/draft04/index.ts +++ b/lib/draft04/index.ts @@ -1,26 +1,27 @@ import addRemoteSchema from "./addRemoteSchema"; -import compileSchema from "../compileSchema"; -import { each } from "../each"; -import { eachSchema } from "../eachSchema"; +import compileSchema from "../compile"; +import createSchemaOf from "../createSchemaOf"; import ERRORS from "../validation/errors"; import FORMATS from "../validation/format"; +import getChildSchemaSelection from "../getChildSchemaSelection"; import getSchema from "../getSchema"; import getTemplate from "../getTemplate"; import isValid from "../isValid"; import KEYWORDS from "../validation/keyword"; import merge from "../utils/merge"; -import { resolveAllOf } from "../features/allOf"; -import { resolveAnyOf } from "../features/anyOf"; -import { resolveOneOf } from "../features/oneOf"; import resolveRef from "../resolveRef.strict"; +import settings from "../config/settings"; import step from "../step"; -import createSchemaOf from "../createSchemaOf"; -import getChildSchemaSelection from "../getChildSchemaSelection"; import TYPES from "../validation/type"; import validate from "../validate"; +import { createNode } from "../schemaNode"; import { DraftConfig, Draft } from "../draft"; +import { each } from "../each"; +import { eachSchema } from "../eachSchema"; import { JsonSchema } from "../types"; -import settings from "../config/settings"; +import { resolveAllOf } from "../features/allOf"; +import { resolveAnyOf } from "../features/anyOf"; +import { resolveOneOf } from "../features/oneOf"; const draft04Config: DraftConfig = { typeKeywords: { @@ -75,6 +76,7 @@ const draft04Config: DraftConfig = { ], null: ["allOf", "anyOf", "enum", "format", "not", "oneOf"] }, + createNode, validateKeyword: KEYWORDS, validateType: TYPES, validateFormat: FORMATS, diff --git a/lib/draft06/index.ts b/lib/draft06/index.ts index f55c8418..9d9ecc06 100644 --- a/lib/draft06/index.ts +++ b/lib/draft06/index.ts @@ -1,26 +1,27 @@ import addRemoteSchema from "../addRemoteSchema"; import compileSchema from "../draft06/compile"; -import { each } from "../each"; -import { eachSchema } from "../eachSchema"; +import createSchemaOf from "../createSchemaOf"; import ERRORS from "../validation/errors"; import FORMATS from "../validation/format"; +import getChildSchemaSelection from "../getChildSchemaSelection"; import getSchema from "../getSchema"; import getTemplate from "../getTemplate"; import isValid from "../isValid"; import KEYWORDS from "../draft06/validation/keyword"; import merge from "../utils/merge"; -import { resolveAllOf } from "../features/allOf"; -import { resolveAnyOf } from "../features/anyOf"; -import { resolveOneOf } from "../features/oneOf"; import resolveRef from "../resolveRef.strict"; -import createSchemaOf from "../createSchemaOf"; -import getChildSchemaSelection from "../getChildSchemaSelection"; +import settings from "../config/settings"; import step from "../step"; -import TYPES from "../draft06/validation/type"; +import TYPES from "../validation/type"; import validate from "../validate"; +import { createNode } from "../schemaNode"; import { DraftConfig, Draft } from "../draft"; +import { each } from "../each"; +import { eachSchema } from "../eachSchema"; import { JsonSchema } from "../types"; -import settings from "../config/settings"; +import { resolveAllOf } from "../features/allOf"; +import { resolveAnyOf } from "../features/anyOf"; +import { resolveOneOf } from "../features/oneOf"; const draft06Config: DraftConfig = { typeKeywords: { @@ -88,6 +89,7 @@ const draft06Config: DraftConfig = { validateFormat: FORMATS, errors: ERRORS, + createNode, addRemoteSchema, compileSchema, createSchemaOf, diff --git a/lib/draft06/validation/type.ts b/lib/draft06/validation/type.ts deleted file mode 100644 index 3a3eba1e..00000000 --- a/lib/draft06/validation/type.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { JsonTypeValidator } from "../../validation/type"; - -/** - * @todo: type is also a keyword, as is properties, items, etc - * - * An instance has one of six primitive types (http://json-schema.org/latest/json-schema-draft.html#rfc.section.4.2) - * or seven in case of ajv https://github.com/epoberezkin/ajv/blob/master/KEYWORDS.md#type - * 1 null, 2 boolean, 3 object, 4 array, 5 number, 6 string (7 integer) - */ -const typeValidators: Record = { - array: (node, value) => - node.draft.typeKeywords.array - .filter((key) => node.schema && node.schema[key] != null) - .map((key) => node.draft.validateKeyword[key](node, value)), - - object: (node, value) => - node.draft.typeKeywords.object - .filter((key) => node.schema && node.schema[key] != null) - .map((key) => node.draft.validateKeyword[key](node, value)), - - string: (node, value) => - node.draft.typeKeywords.string - .filter((key) => node.schema && node.schema[key] != null) - .map((key) => node.draft.validateKeyword[key](node, value)), - - integer: (node, value) => - node.draft.typeKeywords.number - .filter((key) => node.schema && node.schema[key] != null) - .map((key) => node.draft.validateKeyword[key](node, value)), - - number: (node, value) => - node.draft.typeKeywords.number - .filter((key) => node.schema && node.schema[key] != null) - .map((key) => node.draft.validateKeyword[key](node, value)), - - boolean: (node, value) => - node.draft.typeKeywords.boolean - .filter((key) => node.schema && node.schema[key] != null) - .map((key) => node.draft.validateKeyword[key](node, value)), - - null: (node, value) => - node.draft.typeKeywords.null - .filter((key) => node.schema && node.schema[key] != null) - .map((key) => node.draft.validateKeyword[key](node, value)) -}; - -export default typeValidators; diff --git a/lib/draft07/index.ts b/lib/draft07/index.ts index fbcf2849..b53e2439 100644 --- a/lib/draft07/index.ts +++ b/lib/draft07/index.ts @@ -1,26 +1,27 @@ import addRemoteSchema from "../addRemoteSchema"; import compileSchema from "../draft06/compile"; -import { each } from "../each"; -import { eachSchema } from "../eachSchema"; +import createSchemaOf from "../createSchemaOf"; import ERRORS from "../validation/errors"; import FORMATS from "../validation/format"; +import getChildSchemaSelection from "../getChildSchemaSelection"; import getSchema from "../getSchema"; import getTemplate from "../getTemplate"; import isValid from "../isValid"; import KEYWORDS from "../draft06/validation/keyword"; import merge from "../utils/merge"; -import { resolveAllOf } from "../features/allOf"; -import { resolveAnyOf } from "../features/anyOf"; -import { resolveOneOf } from "../features/oneOf"; import resolveRef from "../resolveRef.strict"; -import createSchemaOf from "../createSchemaOf"; -import getChildSchemaSelection from "../getChildSchemaSelection"; +import settings from "../config/settings"; import step from "../step"; -import TYPES from "../draft06/validation/type"; +import TYPES from "../validation/type"; import validate from "../validate"; +import { createNode } from "../schemaNode"; import { DraftConfig, Draft } from "../draft"; +import { each } from "../each"; +import { eachSchema } from "../eachSchema"; import { JsonSchema } from "../types"; -import settings from "../config/settings"; +import { resolveAllOf } from "../features/allOf"; +import { resolveAnyOf } from "../features/anyOf"; +import { resolveOneOf } from "../features/oneOf"; const draft07Config: DraftConfig = { typeKeywords: { @@ -88,6 +89,7 @@ const draft07Config: DraftConfig = { validateFormat: FORMATS, errors: ERRORS, + createNode, addRemoteSchema, compileSchema, createSchemaOf, diff --git a/lib/draft2019/index.ts b/lib/draft2019/index.ts index 69aa54de..024da5cd 100644 --- a/lib/draft2019/index.ts +++ b/lib/draft2019/index.ts @@ -1,26 +1,27 @@ import addRemoteSchema from "../addRemoteSchema"; import compileSchema from "../draft06/compile"; -import { each } from "../each"; -import { eachSchema } from "../eachSchema"; +import createSchemaOf from "../createSchemaOf"; import ERRORS from "../validation/errors"; import FORMATS from "../validation/format"; +import getChildSchemaSelection from "../getChildSchemaSelection"; import getSchema from "../getSchema"; import getTemplate from "../getTemplate"; import isValid from "../isValid"; import KEYWORDS from "./validation/keyword"; import merge from "../utils/merge"; -import { resolveAllOf } from "../features/allOf"; -import { resolveAnyOf } from "../features/anyOf"; -import { resolveOneOf } from "../features/oneOf"; -import createSchemaOf from "../createSchemaOf"; -import getChildSchemaSelection from "../getChildSchemaSelection"; +import resolveRef from "../resolveRef"; +import settings from "../config/settings"; import step from "../step"; -import TYPES from "../draft06/validation/type"; +import TYPES from "../validation/type"; import validate from "../validate"; +import { createNode } from "../schemaNode"; import { DraftConfig, Draft } from "../draft"; +import { each } from "../each"; +import { eachSchema } from "../eachSchema"; import { JsonSchema } from "../types"; -import settings from "../config/settings"; -import resolveRef from "../resolveRef.merge"; +import { resolveAllOf } from "../features/allOf"; +import { resolveAnyOf } from "../features/anyOf"; +import { resolveOneOf } from "../features/oneOf"; const draft2019Config: DraftConfig = { @@ -93,6 +94,7 @@ const draft2019Config: DraftConfig = { validateFormat: FORMATS, errors: ERRORS, + createNode, addRemoteSchema, compileSchema, createSchemaOf, diff --git a/lib/draft2019/validation/keyword.ts b/lib/draft2019/validation/keyword.ts index a7dd12e0..44b45d5d 100644 --- a/lib/draft2019/validation/keyword.ts +++ b/lib/draft2019/validation/keyword.ts @@ -6,7 +6,6 @@ import { validateDependentSchemas, validateDependentRequired } from "../../featu import { JsonValidator } from "../../validation/type"; import { SchemaNode } from "../../schemaNode"; - /** * Get a list of tests to search for a matching pattern to a property */ diff --git a/lib/each.ts b/lib/each.ts index 4004002b..22a7223d 100644 --- a/lib/each.ts +++ b/lib/each.ts @@ -14,7 +14,7 @@ export type EachCallback = (schema: JsonSchema, data: unknown, pointer: JsonPoin * @param [pointer] - pointer to current data. Default to rootPointer */ export function each(schemaNode: SchemaNode, data: any, callback: EachCallback) { - const node = schemaNode.draft.resolveRef(schemaNode); + const node = schemaNode.resolveRef(); const { draft, schema, pointer } = node; callback(schema, data, pointer); const dataType = getTypeOf(data); diff --git a/lib/features/allOf.ts b/lib/features/allOf.ts index bb6256ed..6f0f1b45 100644 --- a/lib/features/allOf.ts +++ b/lib/features/allOf.ts @@ -15,19 +15,19 @@ import { JsonValidator } from "../validation/type"; * when complete this will have much duplication to step.object etc */ export function resolveSchema(node: SchemaNode, data: unknown): SchemaNode | JsonError { - const schema = shallowCloneSchemaNode(node.schema); const ifSchema = resolveIfSchema(node, data); if (ifSchema) { return ifSchema; } + const schema = shallowCloneSchemaNode(node.schema); return node.next(omit(schema, "if", "then", "else")); } export function resolveAllOf(node: SchemaNode, data: any): SchemaNode | JsonError { - const { schema, draft } = node; + const { schema } = node; let mergedSchema = shallowCloneSchemaNode(schema); for (let i = 0; i < schema.allOf.length; i += 1) { - const allOfNode = draft.resolveRef(node.next(schema.allOf[i] as JsonSchema)); + const allOfNode = node.next(schema.allOf[i] as JsonSchema).resolveRef(); // @todo introduce draft.resolveSchema to iteratively resolve const allOfSchema = resolveSchema(allOfNode, data).schema; mergedSchema = mergeSchema(mergedSchema, allOfSchema); @@ -53,7 +53,7 @@ export function mergeAllOfSchema(draft: Draft, schema: JsonSchema): JsonSchema | if (subschema == null) { return; } - const subSchemaNode = draft.resolveRef(createNode(draft, subschema)); + const subSchemaNode = draft.createNode(subschema).resolveRef(); resolvedSchema = mergeSchema(resolvedSchema, subSchemaNode.schema); }); return resolvedSchema; diff --git a/lib/features/anyOf.ts b/lib/features/anyOf.ts index 153dab88..486bafd4 100644 --- a/lib/features/anyOf.ts +++ b/lib/features/anyOf.ts @@ -3,7 +3,6 @@ */ import { mergeSchema } from "../mergeSchema"; import { JsonSchema, JsonError } from "../types"; -import { omit } from "../utils/omit"; import { JsonValidator } from "../validation/type"; import { SchemaNode } from "../schemaNode"; @@ -45,10 +44,7 @@ export function resolveAnyOf(node: SchemaNode, data: any): SchemaNode | JsonErro const { pointer, schema } = node; return node.draft.errors.anyOfError({ pointer, schema, value: data, anyOf: JSON.stringify(anyOf) }); } - - // node.merge(resolvedNode.schema, "anyOf") - const mergedSchema = mergeSchema(node.schema, resolvedNode.schema); - return node.next(omit(mergedSchema, "anyOf")); + return node.merge(resolvedNode.schema, "anyOf"); } /** diff --git a/lib/features/dependencies.ts b/lib/features/dependencies.ts index 809e9ff1..24d6482e 100644 --- a/lib/features/dependencies.ts +++ b/lib/features/dependencies.ts @@ -18,7 +18,7 @@ import { SchemaNode } from "../schemaNode"; * @returns merged json schema defined by dependencies or undefined */ export function resolveDependencies(node: SchemaNode, data: unknown): JsonSchema | undefined { - const { schema, draft } = node; + const { schema } = node; // @draft >= 2019-09 dependentSchemas const dependencies = schema.dependencies ?? schema.dependentSchemas; if (!isObject(dependencies) || !isObject(data)) { @@ -45,8 +45,8 @@ export function resolveDependencies(node: SchemaNode, data: unknown): JsonSchema // dependency schema if (isObject(dependency)) { updated = true; - const dNode = node.next(dependency); - resolvedSchema = mergeSchema(resolvedSchema, draft.resolveRef(dNode).schema); + const dNode = node.next(dependency).resolveRef(); + resolvedSchema = mergeSchema(resolvedSchema, dNode.schema); return; } }); diff --git a/lib/getChildSchemaSelection.ts b/lib/getChildSchemaSelection.ts index 9c8c78cb..36fad9e0 100644 --- a/lib/getChildSchemaSelection.ts +++ b/lib/getChildSchemaSelection.ts @@ -1,6 +1,5 @@ import { Draft } from "./draft"; import { isJsonError, JsonError, JsonSchema } from "./types"; -import { createNode } from "./schemaNode"; /** * Returns a list of possible child-schemas for the given property key. In case of a oneOf selection, multiple schemas @@ -18,10 +17,10 @@ export default function getChildSchemaSelection( schema: JsonSchema = draft.rootSchema ): JsonSchema[] | JsonError { if (schema.oneOf) { - return schema.oneOf.map((item: JsonSchema) => draft.resolveRef(createNode(draft, item)).schema); + return schema.oneOf.map((item: JsonSchema) => draft.createNode(item).resolveRef().schema); } if (schema.items?.oneOf) { - return schema.items.oneOf.map((item: JsonSchema) => draft.resolveRef(createNode(draft, item)).schema); + return schema.items.oneOf.map((item: JsonSchema) => draft.createNode(item).resolveRef().schema); } const node = draft.step(property, schema, {}, "#"); diff --git a/lib/getSchema.ts b/lib/getSchema.ts index fb76eade..226b7a24 100644 --- a/lib/getSchema.ts +++ b/lib/getSchema.ts @@ -1,6 +1,6 @@ import gp, { JsonPath } from "@sagold/json-pointer"; -import { JsonSchema, JsonPointer, isJsonError, JsonError } from "./types"; -import { createNode, SchemaNode } from "./schemaNode"; +import { JsonSchema, JsonPointer, isJsonError } from "./types"; +import { SchemaNode } from "./schemaNode"; import { Draft } from "./draft"; const emptyObject = {} as const; @@ -43,7 +43,7 @@ export type GetSchemaOptions = { export default function getSchema(draft: Draft, options: GetSchemaOptions = emptyObject) { const { pointer = "#", data, schema = draft.rootSchema, withSchemaWarning = false } = options; const path = gp.split(pointer); - const node = draft.resolveRef(createNode(draft, schema)); + const node = draft.createNode(schema).resolveRef(); const result = _getSchema(node, path, data); if (!withSchemaWarning && isJsonError(result) && result.code === "schema-warning") { return undefined; @@ -51,16 +51,12 @@ export default function getSchema(draft: Draft, options: GetSchemaOptions = empt return result; } -function _getSchema( - node: SchemaNode, - path: JsonPath, - data: unknown = emptyObject -): SchemaNode | JsonError { +function _getSchema(node: SchemaNode, path: JsonPath, data: unknown = emptyObject) { if (path.length === 0) { - return node.draft.resolveRef(node); + return node.resolveRef(); } const key = path.shift(); // step key - const nextNode = node.draft.step(key, node.schema, data, node.pointer); // step schema + const nextNode = node.draft.step(node, key, data); // step schema if (isJsonError(nextNode)) { return nextNode; } diff --git a/lib/getTemplate.ts b/lib/getTemplate.ts index f7fe9be8..9027deed 100644 --- a/lib/getTemplate.ts +++ b/lib/getTemplate.ts @@ -5,7 +5,7 @@ import merge from "./utils/merge"; import copy from "./utils/copy"; import settings from "./config/settings"; import { JsonSchema, JsonPointer, isJsonError } from "./types"; -import { createNode, isSchemaNode } from "./schemaNode"; +import { isSchemaNode } from "./schemaNode"; import { Draft } from "./draft"; import { isEmpty } from "./utils/isEmpty"; import { resolveIfSchema } from "./features/if"; @@ -47,7 +47,7 @@ function resolveRef(draft: Draft, schema: JsonSchema, pointer: JsonPointer) { cache[pointer] = cache[pointer] || {}; cache[pointer][$ref] = cache[pointer][$ref] || 0; cache[pointer][$ref] += 1; - return draft.resolveRef(createNode(draft, schema, pointer)).schema; + return draft.createNode(schema, pointer).resolveRef().schema; } function convertValue(type: string, value: any) { @@ -119,7 +119,7 @@ function createTemplateSchema( const allOf: JsonSchema[] = []; let extendedData = copy(data); for (let i = 0; i < schema.allOf.length; i += 1) { - const allNode = createNode(draft, schema.allOf[i], pointer); + const allNode = draft.createNode(schema.allOf[i], pointer); allOf.push(resolveSchema(allNode, extendedData).schema); extendedData = getTemplate(draft, extendedData, { type: schema.type, ...allOf[i] }, `${pointer}/allOf/${i}`, opts); } @@ -182,7 +182,7 @@ function getTemplate( schema = { ...schema.oneOf[0], type }; } else { // find correct schema for data - const oneNode = createNode(draft, schema, pointer); + const oneNode = draft.createNode(schema, pointer); const resolvedNode = resolveOneOfFuzzy(oneNode, data); if (isJsonError(resolvedNode)) { if (data != null && opts.removeInvalidData !== true) { @@ -303,7 +303,7 @@ const TYPE: Record< // @feature dependencies // has to be done after resolving properties so dependency may trigger - const dNode = createNode(draft, schema, pointer); + const dNode = draft.createNode(schema, pointer); let dependenciesSchema = resolveDependencies(dNode, d); if (dependenciesSchema) { dependenciesSchema = mergeSchema(schema, dependenciesSchema); @@ -341,7 +341,7 @@ const TYPE: Record< } // @feature if-then-else - const node = createNode(draft, schema, pointer); + const node = draft.createNode(schema, pointer); const ifSchema = resolveIfSchema(node, d); if (isSchemaNode(ifSchema)) { const additionalData = getTemplate( @@ -419,7 +419,7 @@ const TYPE: Record< const itemCount = Math.max(minItems, d.length); for (let i = 0; i < itemCount; i += 1) { let value = d[i] == null ? template[i] : d[i]; - const oneNode = createNode(draft, templateSchema, pointer); + const oneNode = draft.createNode(templateSchema, pointer); let one = resolveOneOfFuzzy(oneNode, value); if (one == null || isJsonError(one)) { diff --git a/lib/jsoneditor/index.ts b/lib/jsoneditor/index.ts index 7e5a64ff..7a0217f4 100644 --- a/lib/jsoneditor/index.ts +++ b/lib/jsoneditor/index.ts @@ -1,6 +1,6 @@ import merge from "../utils/merge"; import { resolveOneOfFuzzy } from "../features/oneOf"; -import resolveRef from "../resolveRef.merge"; +import resolveRef from "../resolveRef"; import { Draft, DraftConfig } from "../draft"; import { draft07Config } from "../draft07"; import { JsonSchema } from "../types"; diff --git a/lib/reduceSchema.ts b/lib/reduceSchema.ts index aa16c321..17bd0f84 100644 --- a/lib/reduceSchema.ts +++ b/lib/reduceSchema.ts @@ -1,7 +1,5 @@ import { isSchemaNode, SchemaNode } from "./schemaNode"; -import { mergeSchema } from "./mergeSchema"; import { resolveDynamicSchema } from "./resolveDynamicSchema"; -import { omit } from "./utils/omit"; const toOmit = ["allOf", "anyOf", "oneOf", "dependencies", "if", "then", "else"]; @@ -16,8 +14,7 @@ const toOmit = ["allOf", "anyOf", "oneOf", "dependencies", "if", "then", "else"] export function reduceSchema(node: SchemaNode, data: unknown) { const resolvedSchema = resolveDynamicSchema(node, data); if (isSchemaNode(resolvedSchema)) { - const result = mergeSchema(node.schema, resolvedSchema.schema); - return node.next(omit(result, ...toOmit)); + return node.merge(resolvedSchema.schema, ...toOmit); } if (resolvedSchema) { return resolvedSchema; // error diff --git a/lib/resolveDynamicSchema.ts b/lib/resolveDynamicSchema.ts index 1a8cdeaa..75cda168 100644 --- a/lib/resolveDynamicSchema.ts +++ b/lib/resolveDynamicSchema.ts @@ -98,7 +98,7 @@ export function resolveDynamicSchema(schemaNode: SchemaNode, data: unknown) { return resolvedSchema; } - const nestedSchema: JsonSchema | undefined = resolveDynamicSchema(node.next(resolvedSchema), data); + const nestedSchema = resolveDynamicSchema(node.next(resolvedSchema), data); if (isSchemaNode(nestedSchema)) { resolvedSchema = mergeSchema(resolvedSchema, nestedSchema.schema); } diff --git a/lib/resolveRef.strict.ts b/lib/resolveRef.strict.ts index 17a5f4d6..b7f900ba 100644 --- a/lib/resolveRef.strict.ts +++ b/lib/resolveRef.strict.ts @@ -5,18 +5,15 @@ export default function resolveRef(node: SchemaNode): SchemaNode { if (!isSchemaNode(node)) { throw new Error("schema node expected"); } - if (node.schema == null || node.schema.$ref == null) { return node; } - if (node.schema.getRoot) { // we actually always need to resolve the schema like this, since returned subschemas // must resolve relative from their schema const resolvedSchema = node.schema.getRoot().getRef(node.schema); return node.next(resolvedSchema as JsonSchema); } - // tryout - this should never be called, except we missed something const resolvedSchema = node.draft.rootSchema.getRef(node.schema); return node.next(resolvedSchema as JsonSchema); diff --git a/lib/resolveRef.merge.ts b/lib/resolveRef.ts similarity index 83% rename from lib/resolveRef.merge.ts rename to lib/resolveRef.ts index 7d9c65fe..35f3a9c4 100644 --- a/lib/resolveRef.merge.ts +++ b/lib/resolveRef.ts @@ -1,10 +1,7 @@ import { JsonSchema } from "./types"; -import { mergeSchema } from "./mergeSchema"; import { SchemaNode, isSchemaNode } from "./schemaNode"; // 1. https://json-schema.org/draft/2019-09/json-schema-core#scopes - - function resolveRecursiveRef(node: SchemaNode): SchemaNode { const history = node.path; @@ -36,16 +33,15 @@ function resolveRecursiveRef(node: SchemaNode): SchemaNode { * @todo update types * Note: JsonSchema my be false */ -export default function resolveRefMerge(node: SchemaNode): SchemaNode { +export default function resolveRef(node: SchemaNode): SchemaNode { if (!isSchemaNode(node)) { throw new Error("expected node") } if (node.schema == null) { return node; } - if (node.schema.$recursiveRef) { - return resolveRefMerge(resolveRecursiveRef(node)); + return resolveRef(resolveRecursiveRef(node)); } if (node.schema.$ref == null) { return node; @@ -55,8 +51,5 @@ export default function resolveRefMerge(node: SchemaNode): SchemaNode { return node.next(resolvedSchema as JsonSchema); } // @draft >= 2019-09 we now merge schemas: in draft <= 7 $ref is treated as reference, not as schema - const mergedSchema = mergeSchema(node.schema, resolvedSchema); - delete mergedSchema.$ref; - - return node.next(mergedSchema as JsonSchema); + return node.merge(resolvedSchema, "$ref"); } diff --git a/lib/schemaNode.ts b/lib/schemaNode.ts index 8a1c7300..f095492b 100644 --- a/lib/schemaNode.ts +++ b/lib/schemaNode.ts @@ -2,6 +2,21 @@ import { Draft } from "./draft"; import getTypeOf from "./getTypeOf"; import { isObject } from "./utils/isObject"; import { JsonSchema, JsonError, isJsonError } from "./types"; +import { mergeSchema } from "./mergeSchema"; + +function merge(schema: JsonSchema, ...omit: string[]): SchemaNode { + if (schema == null) { + throw new Error(`undefined schema`); + } + const node = this as SchemaNode; + const mergedSchema = mergeSchema(node.schema, schema, ...omit); + return { ...node, schema: mergedSchema, path: [...node.path, node.schema] }; +} + +function resolveRef() { + const node = this as SchemaNode; + return node.draft.resolveRef(node); +} /** * create next node based from current node @@ -31,11 +46,13 @@ function next(schema: JsonSchema, key?: string | number) { } export type SchemaNode = { - draft: Draft, - pointer: string, - schema: JsonSchema, - path: JsonSchema[], - next: typeof next + draft: Draft; + pointer: string; + schema: JsonSchema; + path: JsonSchema[]; + next: typeof next; + merge: typeof merge; + resolveRef: typeof resolveRef; } export function isSchemaNode(value: unknown): value is SchemaNode { @@ -44,11 +61,5 @@ export function isSchemaNode(value: unknown): value is SchemaNode { } export function createNode(draft: Draft, schema: JsonSchema, pointer: string = "#"): SchemaNode { - return { - draft, - pointer, - schema, - path: [], - next - } + return { draft, pointer, schema, path: [], next, merge, resolveRef }; } diff --git a/lib/utils/shallowCloneSchema.ts b/lib/utils/shallowCloneSchema.ts index f3667b11..1f1ae010 100644 --- a/lib/utils/shallowCloneSchema.ts +++ b/lib/utils/shallowCloneSchema.ts @@ -2,9 +2,6 @@ import { JsonSchema } from "../types"; export function shallowCloneSchemaNode(node: JsonSchema) { const result = { ...node }; - Object.defineProperty(result, "__compiled", { enumerable: false, value: true }); - Object.defineProperty(result, "__ref", { enumerable: false, value: node.__ref }); Object.defineProperty(result, "getOneOfOrigin", { enumerable: false, value: node.getOneOfOrigin }); - Object.defineProperty(result, "getRoot", { enumerable: false, value: node.getRoot }); return result; } diff --git a/lib/validate.ts b/lib/validate.ts index efd0009b..9b99d0b1 100644 --- a/lib/validate.ts +++ b/lib/validate.ts @@ -17,8 +17,6 @@ function getJsonSchemaType(value: unknown, expectedType: string | string[]): JST return jsType; } -// let prev: any; - /** * Validates data with json schema * @@ -33,7 +31,7 @@ export default function validate(node: SchemaNode, value: unknown): Array