json-schema-library 11.0.1 → 11.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.ts CHANGED
@@ -3,7 +3,7 @@ export type { CompileOptions } from "./src/compileSchema";
3
3
  export type { Context, SchemaNode, GetNodeOptions, ValidateReturnType } from "./src/SchemaNode";
4
4
  export type { DataNode } from "./src/methods/toDataNodes";
5
5
  export type { Draft, DraftVersion } from "./src/Draft";
6
- export type { JsonError, JsonAnnotation, JsonPointer, JsonSchema, OptionalNodeOrError, NodeOrError } from "./src/types";
6
+ export type { JsonError, JsonAnnotation, JsonPointer, JsonSchema, BooleanSchema, OptionalNodeOrError, NodeOrError } from "./src/types";
7
7
  export type {
8
8
  Keyword,
9
9
  Maybe,
@@ -37,7 +37,7 @@ export type { Annotation, AnnotationData, ErrorConfig } from "./src/types";
37
37
  // utilities
38
38
  export { getTypeOf } from "./src/utils/getTypeOf";
39
39
  export { isReduceable } from "./src/SchemaNode";
40
- export { isJsonError, isJsonAnnotation, isAnnotation, isSchemaNode } from "./src/types";
40
+ export { isJsonError, isJsonAnnotation, isAnnotation, isSchemaNode, isJsonSchema, isBooleanSchema } from "./src/types";
41
41
  export { extendDraft, addKeywords } from "./src/Draft";
42
42
  export { mergeNode } from "./src/mergeNode";
43
43
  export { mergeSchema } from "./src/utils/mergeSchema";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-schema-library",
3
- "version": "11.0.1",
3
+ "version": "11.0.3",
4
4
  "description": "Customizable and hackable json-validator and json-schema utilities for traversal, data generation and validation",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",
@@ -12,7 +12,12 @@
12
12
  },
13
13
  "./package.json": "./package.json"
14
14
  },
15
+ "engines": {
16
+ "yarn": ">=1.22.0",
17
+ "npm": "use yarn instead"
18
+ },
15
19
  "scripts": {
20
+ "preinstall": "npx only-allow yarn",
16
21
  "coverage": "nyc npm run test --reporter=lcov",
17
22
  "dist": "tsdown -f esm -f cjs --minify; yarn dist:iife",
18
23
  "dist:iife": "tsdown --config tsdown.iife.config.ts --no-clean --minify; mv dist/index.iife.js dist/jlib.js",
@@ -33,7 +38,7 @@
33
38
  "test:6:ci": "DISABLE_LOG=true mocha -R json 'src/tests/spec/draft06.spec.ts' > test-result-spec6.json; exit 0",
34
39
  "test:7": "mocha 'src/tests/spec/draft07.spec.ts'",
35
40
  "test:7:ci": "DISABLE_LOG=true mocha -R json 'src/tests/spec/draft07.spec.ts' > test-result-spec7.json; exit 0",
36
- "test:inspect": "yarn test:unit --inspect-brk",
41
+ "test:inspect": "NODE_OPTIONS='--inspect-brk' mocha 'src/**/*.test.ts'",
37
42
  "test:spec": "mocha 'src/tests/spec/*.spec.ts'",
38
43
  "test:unit": "mocha 'src/**/*.test.ts'",
39
44
  "test:unit:ci": "DISABLE_LOG=true mocha -R json 'src/**/*.test.ts' -R json > test-result-unit.json; exit 0"
package/src/SchemaNode.ts CHANGED
@@ -15,7 +15,9 @@ import { Draft } from "./Draft";
15
15
  import { toSchemaNodes } from "./methods/toSchemaNodes";
16
16
  import {
17
17
  isJsonError,
18
+ isJsonSchema,
18
19
  JsonSchema,
20
+ BooleanSchema,
19
21
  JsonError,
20
22
  AnnotationData,
21
23
  DefaultErrors,
@@ -38,7 +40,7 @@ import { getNode } from "./getNode";
38
40
  import { getNodeChild } from "./getNodeChild";
39
41
  import { DataNode } from "./methods/toDataNodes";
40
42
 
41
- const { DYNAMIC_PROPERTIES, REGEX_FLAGS } = settings;
43
+ const { DYNAMIC_PROPERTIES, REGEX_FLAGS, DECLARATOR_ONEOF } = settings;
42
44
 
43
45
  export function isSchemaNode(value: unknown): value is SchemaNode {
44
46
  return isObject(value) && Array.isArray(value?.reducers) && Array.isArray(value?.resolvers);
@@ -189,17 +191,39 @@ export interface SchemaNode extends SchemaNodeMethodsType {
189
191
  * Fixed SchemaNode mixin methods
190
192
  */
191
193
  interface SchemaNodeMethodsType {
192
- compileSchema(schema: JsonSchema, evaluationPath?: string, schemaLocation?: string, dynamicId?: string): SchemaNode;
194
+ compileSchema(schema: JsonSchema | BooleanSchema, evaluationPath?: string, schemaLocation?: string, dynamicId?: string): SchemaNode;
193
195
  createError<T extends string = DefaultErrors>(code: T, data: AnnotationData, message?: string): JsonError;
194
196
  createAnnotation<T extends string = DefaultErrors>(code: T, data: AnnotationData, message?: string): JsonAnnotation;
195
197
  createSchema(data?: unknown): JsonSchema;
196
198
 
197
- // getNode overloads
199
+ /**
200
+ * Returns a node matching the given location (pointer) in data
201
+ *
202
+ * - the returned node will have a **reduced schema** based on given input data
203
+ * - return returned node $ref is resolved
204
+ *
205
+ * To resolve dynamic schema where the type of JSON Schema is evaluated by
206
+ * its value, a data object has to be passed in options.
207
+ *
208
+ * Per default this function will return `undefined` schema for valid properties
209
+ * that do not have a defined schema. Use the option `withSchemaWarning: true` to
210
+ * receive an error with `code: schema-warning` containing the location of its
211
+ * last evaluated json-schema.
212
+ *
213
+ * @returns { node } or { error } where node can also be undefined (valid but undefined)
214
+ */
198
215
  getNode(pointer: string, data: unknown, options: { withSchemaWarning: true } & GetNodeOptions): NodeOrError;
199
216
  getNode(pointer: string, data: unknown, options: { createSchema: true } & GetNodeOptions): NodeOrError;
200
217
  getNode(pointer: string, data?: unknown, options?: GetNodeOptions): OptionalNodeOrError;
201
218
 
202
- // getNodeChild overloads
219
+ /**
220
+ * Returns the child for the given property-name or array-index
221
+ *
222
+ * - the returned child node is **not reduced**
223
+ * - a child node $ref is resolved
224
+ *
225
+ * @returns { node } or { error } where node can also be undefined (valid but undefined)
226
+ */
203
227
  getNodeChild(
204
228
  key: string | number,
205
229
  data: unknown,
@@ -219,7 +243,7 @@ interface SchemaNodeMethodsType {
219
243
  ): OptionalNodeOrError;
220
244
  resolveRef: (args?: { pointer?: string; path?: ValidationPath }) => SchemaNode;
221
245
  validate(data: unknown, pointer?: string, path?: ValidationPath): ValidateReturnType;
222
- addRemoteSchema(url: string, schema: JsonSchema): SchemaNode;
246
+ addRemoteSchema(url: string, schema: JsonSchema | BooleanSchema): SchemaNode;
223
247
  toSchemaNodes(): SchemaNode[];
224
248
  toDataNodes(data: unknown, pointer?: string): DataNode[];
225
249
  toJSON(): unknown;
@@ -431,7 +455,7 @@ export const SchemaNodeMethods = {
431
455
  }
432
456
 
433
457
  // remove dynamic properties of node
434
- workingNode.schema = omit(workingNode.schema, ...DYNAMIC_PROPERTIES);
458
+ workingNode.schema = omit(workingNode.schema, DECLARATOR_ONEOF, ...DYNAMIC_PROPERTIES);
435
459
  // @ts-expect-error string accessing schema props
436
460
  DYNAMIC_PROPERTIES.forEach((prop) => (workingNode[prop] = undefined));
437
461
  return { node: workingNode, error: undefined };
@@ -472,12 +496,16 @@ export const SchemaNodeMethods = {
472
496
  * Register a JSON Schema as a remote-schema to be resolved by $ref, $anchor, etc
473
497
  * @returns the current node (not the remote schema-node)
474
498
  */
475
- addRemoteSchema(url: string, schema: JsonSchema): SchemaNode {
476
- // @draft >= 6
499
+ addRemoteSchema(url: string, schema: JsonSchema | BooleanSchema): SchemaNode {
500
+ // @draft >= 6
501
+ if (isJsonSchema(schema)) {
477
502
  schema.$id = resolveUri(schema.$id || url);
503
+ }
504
+
478
505
  const node = this as SchemaNode;
479
506
  const { context } = node;
480
- const draft = getDraft(context.drafts, schema?.$schema ?? context.rootNode.schema?.$schema);
507
+ const schemaId = isJsonSchema(schema) ? schema.$schema : undefined;
508
+ const draft = getDraft(context.drafts, schemaId ?? context.rootNode.schema?.$schema);
481
509
 
482
510
  const remoteNode: SchemaNode = {
483
511
  evaluationPath: "#",
@@ -6,7 +6,7 @@ import { draft07 } from "./draft07";
6
6
  import { draft2019 } from "./draft2019";
7
7
  import { draft2020 } from "./draft2020";
8
8
  import { pick } from "./utils/pick";
9
- import { JsonSchema, Draft } from "./types";
9
+ import { JsonSchema, BooleanSchema, Draft, isJsonSchema } from "./types";
10
10
  import { TemplateOptions } from "./methods/getData";
11
11
  import { SchemaNode, SchemaNodeMethods, addKeywords, isSchemaNode } from "./SchemaNode";
12
12
  import settings from "./settings";
@@ -31,10 +31,10 @@ function getDraft(drafts: Draft[], $schema: string) {
31
31
  * wrapping each schema with utilities and as much preevaluation as possible. Each
32
32
  * node will be reused for each task, but will create a compiledNode for bound data.
33
33
  */
34
- export function compileSchema(schema: JsonSchema, options: CompileOptions = {}) {
34
+ export function compileSchema(schema: JsonSchema | BooleanSchema, options: CompileOptions = {}) {
35
35
  let formatAssertion = options.formatAssertion ?? true;
36
36
  const drafts = options.drafts ?? defaultDrafts;
37
- const draft = getDraft(drafts, schema?.$schema);
37
+ const draft = getDraft(drafts, isJsonSchema(schema) ? schema.$schema : undefined);
38
38
 
39
39
  const node: SchemaNode = {
40
40
  evaluationPath: "#",
@@ -44,7 +44,7 @@ export function compileSchema(schema: JsonSchema, options: CompileOptions = {})
44
44
  reducers: [],
45
45
  resolvers: [],
46
46
  validators: [],
47
- schema,
47
+ schema: schema as JsonSchema,
48
48
  // @ts-expect-error self-reference added later
49
49
  context: {
50
50
  remotes: {},
@@ -60,7 +60,7 @@ export function compileSchema(schema: JsonSchema, options: CompileOptions = {})
60
60
  };
61
61
 
62
62
  node.context.rootNode = node;
63
- node.context.remotes[schema?.$id ?? "#"] = node;
63
+ node.context.remotes[(isJsonSchema(schema) ? schema.$id : undefined) ?? "#"] = node;
64
64
 
65
65
  if (options.remote) {
66
66
  const metaSchema = getRef(node, node.schema.$schema);
@@ -1,14 +1,14 @@
1
1
  import { extendDraft } from "./Draft";
2
- import { draft2019 } from "./draft2019";
2
+ import { draft2020 } from "./draft2020";
3
3
  import { oneOfFuzzyKeyword } from "./keywords/oneOf";
4
4
  import { render } from "./errors/render";
5
5
 
6
6
  /**
7
- * @draft-editor https://json-schema.org/draft/2019-09/release-notes
7
+ * @draft-editor https://json-schema.org/draft/2020-12/release-notes
8
8
  *
9
- * Uses Draft 2019-09 and changes resolveOneOf to be fuzzy
9
+ * Uses Draft 2020-12 and changes resolveOneOf to be fuzzy
10
10
  */
11
- export const draftEditor = extendDraft(draft2019, {
11
+ export const draftEditor = extendDraft(draft2020, {
12
12
  $schemaRegEx: ".",
13
13
  keywords: [oneOfFuzzyKeyword],
14
14
  errors: {
@@ -45,12 +45,13 @@ export const errors = {
45
45
  "min-items-one-error": "At least one item is required in `{{pointer}}`",
46
46
  "min-length-error": "Value `{{pointer}}` should have a minimum length of `{{minLength}}`, but got `{{length}}`.",
47
47
  "min-length-one-error": "A value is required in `{{pointer}}`",
48
- "missing-one-of-declarator-error": "Missing oneOf declarator `{{declarator}}` in `{{pointer}}`",
48
+ "missing-one-of-declarator-error": "Missing oneOf declarator `{{declarator}}` in schema `{{schemaLocation}}`",
49
49
  "min-properties-error":
50
50
  "Too few properties in `{{pointer}}`, should be at least `{{minProperties}}`, but got `{{length}}`",
51
51
  "missing-array-item-error": "Array at '{{pointer}}' has a missing item at '{{key}}'",
52
52
  "missing-dependency-error": "The required propery '{{missingProperty}}' in `{{pointer}}` is missing",
53
- "missing-one-of-property-error": "Value at `{{pointer}}` property: `{{property}}`",
53
+ "missing-one-of-property-error":
54
+ "Value at `{{pointer}}` must be object or array and have a property ${oneOfProperty}: ${value}",
54
55
  "multiple-of-error": "Expected `{{value}}` in `{{pointer}}` to be multiple of `{{multipleOf}}`",
55
56
  "multiple-one-of-error": "Value `{{value}}` should not match multiple schemas in oneOf `{{matches}}`",
56
57
  "no-additional-properties-error": "Additional property `{{property}}` in `{{pointer}}` is not allowed",
@@ -0,0 +1,43 @@
1
+ import { compileSchema } from "./compileSchema";
2
+ import { strict as assert } from "assert";
3
+ import { isSchemaNode } from "./types";
4
+ import settings from "./settings";
5
+ const DECLARATOR_ONEOF = settings.DECLARATOR_ONEOF;
6
+
7
+ describe("getNodeChild.oneOfProperty", () => {
8
+ describe("", () => {
9
+ it("should return resolved reference (reduced oneOfNode)", () => {
10
+ const node = compileSchema({
11
+ items: {
12
+ [DECLARATOR_ONEOF]: "name",
13
+ oneOf: [{ $ref: "#/$defs/first" }, { $ref: "#/$defs/second" }]
14
+ },
15
+ $defs: {
16
+ first: {
17
+ properties: { name: { type: "string", const: "first" }, title: { type: "number" } }
18
+ },
19
+ second: {
20
+ properties: { name: { type: "string", const: "second" }, title: { type: "number" } }
21
+ }
22
+ }
23
+ });
24
+
25
+ // precondition
26
+ const reducedItems = node.items?.reduceNode({ name: "second" });
27
+ assert(reducedItems && reducedItems.node, "should have successfully resolved items schema directly");
28
+ assert.deepEqual(
29
+ reducedItems.node?.schema,
30
+ {
31
+ properties: { name: { type: "string", const: "second" }, title: { type: "number" } }
32
+ },
33
+ "should have correctly resolved items-schema directly"
34
+ );
35
+
36
+ const { node: res } = node.getNode("#/0", [{ name: "second" }]);
37
+ assert(isSchemaNode(res), "expected result to a node");
38
+ assert.deepEqual(res.schema, {
39
+ properties: { name: { type: "string", const: "second" }, title: { type: "number" } }
40
+ });
41
+ });
42
+ });
43
+ });
@@ -520,7 +520,7 @@ describe("compileSchema : getNode", () => {
520
520
  assert.deepEqual(node.schema, { type: "array", minItems: 2 });
521
521
  });
522
522
 
523
- it("should mrege title from local schema", () => {
523
+ it("should merge title from local schema", () => {
524
524
  const { node } = compileSchema({
525
525
  type: "array",
526
526
  prefixItems: [{ title: "from ref", $ref: "/$defs/target" }],
package/src/getNode.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { GetNodeOptions, SchemaNode } from "./SchemaNode";
1
+ import { GetNodeOptions, isSchemaNode, SchemaNode } from "./SchemaNode";
2
2
  import { isJsonError, NodeOrError, OptionalNodeOrError } from "./types";
3
3
  import { split } from "@sagold/json-pointer";
4
4
  import { getValue } from "./utils/getValue";
@@ -7,9 +7,13 @@ import { getValue } from "./utils/getValue";
7
7
  export function getNode(pointer: string, data: unknown, options: { withSchemaWarning: true } & GetNodeOptions): NodeOrError;
8
8
  export function getNode(pointer: string, data: unknown, options: { createSchema: true } & GetNodeOptions): NodeOrError;
9
9
  export function getNode(pointer: string, data?: unknown, options?: GetNodeOptions): OptionalNodeOrError;
10
+
10
11
  /**
11
12
  * Returns a node containing JSON Schema of a data JSON Pointer.
12
13
  *
14
+ * - the returned node will have a reduced schema based on given input data
15
+ * - the returned node $ref is resolved
16
+ *
13
17
  * To resolve dynamic schema where the type of JSON Schema is evaluated by
14
18
  * its value, a data object has to be passed in options.
15
19
  *
@@ -49,6 +53,15 @@ export function getNode(
49
53
  currentNode = result.node;
50
54
  data = getValue(data, keys[i]);
51
55
  }
52
- const result = currentNode.resolveRef(options);
53
- return isJsonError(result) ? { node: undefined, error: result } : { node: result, error: undefined };
56
+
57
+ const { node: reducedNode, error: reduceError } = currentNode.resolveRef(options).reduceNode(data);
58
+
59
+ if (isJsonError(reduceError)) {
60
+ return { node: undefined, error: reduceError };
61
+ }
62
+ if (isSchemaNode(reducedNode)) {
63
+ return { node: reducedNode, error: undefined };
64
+ }
65
+
66
+ return { error: undefined };
54
67
  }
@@ -2,67 +2,73 @@ import { GetNodeOptions, isSchemaNode, SchemaNode } from "./SchemaNode";
2
2
  import { isJsonError, NodeOrError, OptionalNodeOrError } from "./types";
3
3
  import { getValue } from "./utils/getValue";
4
4
 
5
- // prettier-ignore
6
- export function getNodeChild(key: string | number, data: unknown, options: { withSchemaWarning: true } & GetNodeOptions): NodeOrError;
7
- // prettier-ignore
8
- export function getNodeChild(key: string | number, data: unknown, options: { createSchema: true } & GetNodeOptions): NodeOrError;
5
+ export function getNodeChild(key: string | number, data: unknown, options: { withSchemaWarning: true } & GetNodeOptions): NodeOrError; // prettier-ignore
6
+ export function getNodeChild(key: string | number, data: unknown, options: { createSchema: true } & GetNodeOptions): NodeOrError; // prettier-ignore
9
7
  export function getNodeChild(key: string | number, data?: unknown, options?: GetNodeOptions): OptionalNodeOrError;
10
8
 
11
9
  /**
12
- * @returns child node identified by property as SchemaNode
10
+ * Returns the child for the given property-name or array-index
11
+ *
12
+ * - the returned child node is **not reduced**
13
+ * - a child node $ref is resolved
14
+ *
15
+ * @returns { node } or { error } where node can also be undefined (valid but undefined)
13
16
  */
14
17
  export function getNodeChild(
15
18
  key: string | number,
16
19
  data?: unknown,
17
20
  options: GetNodeOptions = {}
18
- ): OptionalNodeOrError | NodeOrError | object {
21
+ ): OptionalNodeOrError | NodeOrError {
19
22
  options.path = options.path ?? [];
20
-
21
23
  options.withSchemaWarning = options.withSchemaWarning ?? false;
22
24
  options.pointer = options.pointer ?? "#";
23
25
  const { path, pointer } = options;
24
26
 
27
+ // reduce parent
25
28
  // @ts-expect-error implicitely any
26
- let node = this as SchemaNode;
27
- if (node.reducers.length) {
28
- const result = node.reduceNode(data, { key, path, pointer });
29
+ let parentNode = this as SchemaNode;
30
+ if (parentNode.reducers.length) {
31
+ const result = parentNode.reduceNode(data, { key, path, pointer });
29
32
  if (result.error) {
30
33
  return result;
31
34
  }
32
35
  if (isSchemaNode(result.node)) {
33
- node = result.node;
36
+ parentNode = result.node;
34
37
  }
35
38
  }
36
39
 
37
- for (const resolver of node.resolvers) {
38
- const schemaNode = resolver({ data, key, node });
39
- if (isSchemaNode(schemaNode)) {
40
- return { node: schemaNode.resolveRef({ pointer, path }), error: undefined };
41
- }
40
+ // find child node
41
+ for (const resolver of parentNode.resolvers) {
42
+ const schemaNode = resolver({ data, key, node: parentNode });
43
+ // a matching resolver found an error, return
42
44
  if (isJsonError(schemaNode)) {
43
45
  return { node: undefined, error: schemaNode };
44
46
  }
47
+ // a matching resolver found a child node, return
48
+ if (isSchemaNode(schemaNode)) {
49
+ return { node: schemaNode.resolveRef({ pointer, path }), error: undefined };
50
+ }
45
51
  }
46
52
 
47
- const referencedNode = node.resolveRef({ path });
48
- if (referencedNode !== node) {
49
- return referencedNode.getNodeChild(key, data, options);
50
- }
51
-
53
+ // no child node was found, but the child node is valid
52
54
  if (options.createSchema === true) {
53
- const newNode = node.compileSchema(
54
- node.createSchema(getValue(data, key)),
55
- `${node.evaluationPath}/additional`,
56
- `${node.schemaLocation}/additional`
55
+ const newNode = parentNode.compileSchema(
56
+ parentNode.createSchema(getValue(data, key)),
57
+ `${parentNode.evaluationPath}/additional`,
58
+ `${parentNode.schemaLocation}/additional`
57
59
  );
58
60
  return { node: newNode, error: undefined };
59
61
  }
60
62
 
61
63
  if (options.withSchemaWarning === true) {
62
- const error = node.createError("schema-warning", { pointer, value: data, schema: node.schema, key });
64
+ const error = parentNode.createError("schema-warning", {
65
+ pointer,
66
+ value: data,
67
+ schema: parentNode.schema,
68
+ key
69
+ });
63
70
  return { node: undefined, error };
64
71
  }
65
72
 
66
- // throw new Error("getNodeChild failed retrieving node or error");
67
- return {};
73
+ return { node: undefined };
68
74
  }
@@ -1,6 +1,6 @@
1
1
  import { strict as assert } from "assert";
2
2
  import { compileSchema } from "../compileSchema";
3
- import { isJsonError } from "../types";
3
+ import { isJsonError, isSchemaNode } from "../types";
4
4
  import { reduceOneOfDeclarator, reduceOneOfFuzzy } from "./oneOf";
5
5
  import settings from "../settings";
6
6
  import { draftEditor } from "../draftEditor";
@@ -350,33 +350,56 @@ describe("keyword : oneof-property : reduce", () => {
350
350
  assert(isJsonError(res), "expected result to be an error");
351
351
  assert.deepEqual(res.code, "missing-one-of-property-error");
352
352
  });
353
- });
354
353
 
355
- describe("array", () => {
356
- it("should return an error if no oneOfProperty could be matched", () => {
354
+ it("should return correct reference", () => {
357
355
  const node = compileSchema({
358
- oneOf: [
359
- {
360
- type: "object",
361
- required: ["title"],
362
- properties: {
363
- title: {
364
- type: "string"
365
- }
366
- }
356
+ [DECLARATOR_ONEOF]: "name",
357
+ oneOf: [{ $ref: "#/$defs/first" }, { $ref: "#/$defs/second" }],
358
+ $defs: {
359
+ first: {
360
+ properties: { name: { type: "string", const: "first" }, title: { type: "number" } }
361
+ },
362
+ second: {
363
+ properties: { name: { type: "string", const: "second" }, title: { type: "number" } }
367
364
  }
368
- ]
365
+ }
366
+ });
367
+ const res = reduceOneOfDeclarator({ node, data: { name: "second" }, pointer: "#", path: [] });
368
+ assert(isSchemaNode(res), "expected result to be a node");
369
+ assert.deepEqual(res.schema, {
370
+ properties: { name: { type: "string", const: "second" }, title: { type: "number" } }
371
+ });
372
+ });
373
+
374
+ it("should return correct reference even if data is not fully valid", () => {
375
+ const node = compileSchema({
376
+ [DECLARATOR_ONEOF]: "name",
377
+ oneOf: [{ $ref: "#/$defs/first" }, { $ref: "#/$defs/second" }],
378
+ $defs: {
379
+ first: {
380
+ properties: { name: { type: "string", const: "first" }, title: { type: "number" } }
381
+ },
382
+ second: {
383
+ properties: { name: { type: "string", const: "second" }, title: { type: "number" } }
384
+ }
385
+ }
369
386
  });
370
387
  const res = reduceOneOfDeclarator({
371
388
  node,
372
- data: { name: "2", title: "not a number" },
389
+ data: { name: "first", title: "not a number" },
373
390
  pointer: "#",
374
391
  path: []
375
392
  });
376
- assert(isJsonError(res), "expected result to be an error");
377
- assert.deepEqual(res.code, "missing-one-of-property-error");
393
+ assert(isSchemaNode(res), "expected result to be a node");
394
+ assert.deepEqual(res.schema, {
395
+ properties: { name: { type: "string", const: "first" }, title: { type: "number" } }
396
+ });
378
397
  });
379
398
  });
399
+
400
+ describe("array", () => {
401
+ // TODO test access on array-item as oneOfProperty id (oneOfProperty: "0")
402
+ });
380
403
  });
381
404
 
382
405
  describe("keyword : oneof-fuzzy : validate", () => {
@@ -101,42 +101,57 @@ function reduceOneOf({ node, data, pointer, path }: Omit<JsonSchemaReducerParams
101
101
  });
102
102
  }
103
103
 
104
+ /**
105
+ * Returns matching oneOf schema identified by matching schema for oneOfProperty
106
+ */
104
107
  export function reduceOneOfDeclarator({ node, data, pointer, path }: Omit<JsonSchemaReducerParams, "key">) {
105
108
  if (node.oneOf == null) {
106
109
  return;
107
110
  }
108
111
 
109
- const errors: ValidationReturnType = [];
110
112
  const oneOfProperty = node.schema[DECLARATOR_ONEOF];
111
- const oneOfValue = getValue(data, oneOfProperty);
113
+ const oneOfPropertyValue = getValue(data, oneOfProperty);
112
114
 
113
- if (oneOfValue === undefined) {
115
+ // in this case, we also fail when data undefined as this always is valid,
116
+ // but not in context on an expected oneOfProperty
117
+ if (data === undefined || oneOfPropertyValue === undefined) {
114
118
  return node.createError("missing-one-of-property-error", {
115
- property: oneOfProperty,
119
+ oneOfProperty,
116
120
  pointer,
117
121
  schema: node.schema,
118
122
  value: data
119
123
  });
120
124
  }
121
125
 
126
+ // find oneOf schema that has a matching oneOfProperty to the current input data
127
+ // TODO throw an error if multiple matches were found
128
+ const errors: ValidationReturnType = [];
122
129
  for (let i = 0; i < node.oneOf.length; i += 1) {
123
130
  const { node: resultNode } = node.oneOf[i].getNodeChild(oneOfProperty, data);
124
131
  if (!isSchemaNode(resultNode)) {
132
+ // one of the oneOf schemas has a missing oneOfTypeProperty
133
+ // TODO this still might succeed
134
+ // TODO there is a possibility this throws an invalid error as we use input data
125
135
  return node.createError("missing-one-of-declarator-error", {
126
136
  declarator: DECLARATOR_ONEOF,
127
137
  oneOfProperty,
128
- schemaPointer: node.oneOf[i].schemaLocation,
138
+ schemaLocation: node.oneOf[i].schemaLocation,
129
139
  pointer: `${pointer}/oneOf/${i}`,
130
140
  schema: node.schema,
131
141
  value: data
132
142
  });
133
143
  }
134
144
 
135
- const result = sanitizeErrors(validateNode(resultNode, oneOfValue, pointer, path));
136
- // result = result.filter(errorOrPromise);
145
+ // collect errors in case we fail finding a matching schema
146
+ const result = sanitizeErrors(
147
+ validateNode(resultNode, oneOfPropertyValue, `${pointer}/${oneOfProperty}`, path)
148
+ );
149
+
137
150
  if (result.length > 0) {
138
151
  errors.push(...result);
139
152
  } else {
153
+ // return at once when we found a schema
154
+ // TODO should check all oneOf-schema
140
155
  const { node: reducedNode } = node.oneOf[i].reduceNode(data, { pointer, path });
141
156
  if (reducedNode) {
142
157
  reducedNode.oneOfIndex = i; // @evaluation-info
@@ -147,7 +162,7 @@ export function reduceOneOfDeclarator({ node, data, pointer, path }: Omit<JsonSc
147
162
 
148
163
  return node.createError("one-of-property-error", {
149
164
  property: oneOfProperty,
150
- value: oneOfValue,
165
+ value: data,
151
166
  pointer,
152
167
  schema: node.schema,
153
168
  errors
@@ -34,6 +34,24 @@ describe("getChildSelection", () => {
34
34
  );
35
35
  });
36
36
 
37
+ it("should return resolved $ref", () => {
38
+ const result = compileSchema({
39
+ type: "array",
40
+ prefixItems: [{ $ref: "#/$defs/first" }, { $ref: "#/$defs/second" }],
41
+ $defs: {
42
+ first: { type: "string" },
43
+ second: { type: "number" }
44
+ }
45
+ }).getChildSelection(1);
46
+
47
+ assert(!isJsonError(result));
48
+ assert.deepEqual(result.length, 1);
49
+ assert.deepEqual(
50
+ result.map((n) => n.schema),
51
+ [{ type: "number" }]
52
+ );
53
+ });
54
+
37
55
  it("should return an empty array if items schema is undefined", () => {
38
56
  const result = compileSchema({
39
57
  type: "array",
@@ -5,7 +5,7 @@ import { isSchemaNode, JsonError, SchemaNode } from "../types";
5
5
  * could be added at the given property (e.g. item-index), thus an array of options is returned. In all other cases
6
6
  * a list with a single item will be returned
7
7
  */
8
- export function getChildSelection(node: SchemaNode, property: string | number): SchemaNode[] | JsonError {
8
+ export function getChildSelection(node: SchemaNode, property: string | number) {
9
9
  if (node.oneOf) {
10
10
  return node.oneOf.map((childNode: SchemaNode) => childNode.resolveRef());
11
11
  }
@@ -302,7 +302,6 @@ describe("compileSchema : reduceNode", () => {
302
302
  }).reduceNode({ id: "second" });
303
303
  assert.deepEqual(node.schema, {
304
304
  type: "object",
305
- oneOfProperty: "id",
306
305
  properties: { id: { const: "second" }, one: { type: "number" } }
307
306
  });
308
307
  });
package/src/types.ts CHANGED
@@ -9,6 +9,13 @@ export type BooleanSchema = boolean;
9
9
  export interface JsonSchema {
10
10
  [keyword: string]: any;
11
11
  }
12
+ export function isJsonSchema(value: unknown): value is JsonSchema {
13
+ return isObject(value);
14
+ }
15
+
16
+ export function isBooleanSchema(value: unknown): value is BooleanSchema {
17
+ return typeof value === "boolean";
18
+ }
12
19
 
13
20
  export type JsonPointer = string;
14
21