json-schema-library 11.0.0 → 11.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-schema-library",
3
- "version": "11.0.0",
3
+ "version": "11.0.2",
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",
@@ -33,7 +33,7 @@
33
33
  "test:6:ci": "DISABLE_LOG=true mocha -R json 'src/tests/spec/draft06.spec.ts' > test-result-spec6.json; exit 0",
34
34
  "test:7": "mocha 'src/tests/spec/draft07.spec.ts'",
35
35
  "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",
36
+ "test:inspect": "NODE_OPTIONS='--inspect-brk' mocha 'src/**/*.test.ts'",
37
37
  "test:spec": "mocha 'src/tests/spec/*.spec.ts'",
38
38
  "test:unit": "mocha 'src/**/*.test.ts'",
39
39
  "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
@@ -38,7 +38,7 @@ import { getNode } from "./getNode";
38
38
  import { getNodeChild } from "./getNodeChild";
39
39
  import { DataNode } from "./methods/toDataNodes";
40
40
 
41
- const { DYNAMIC_PROPERTIES, REGEX_FLAGS } = settings;
41
+ const { DYNAMIC_PROPERTIES, REGEX_FLAGS, DECLARATOR_ONEOF } = settings;
42
42
 
43
43
  export function isSchemaNode(value: unknown): value is SchemaNode {
44
44
  return isObject(value) && Array.isArray(value?.reducers) && Array.isArray(value?.resolvers);
@@ -194,12 +194,34 @@ interface SchemaNodeMethodsType {
194
194
  createAnnotation<T extends string = DefaultErrors>(code: T, data: AnnotationData, message?: string): JsonAnnotation;
195
195
  createSchema(data?: unknown): JsonSchema;
196
196
 
197
- // getNode overloads
197
+ /**
198
+ * Returns a node matching the given location (pointer) in data
199
+ *
200
+ * - the returned node will have a **reduced schema** based on given input data
201
+ * - return returned node $ref is resolved
202
+ *
203
+ * To resolve dynamic schema where the type of JSON Schema is evaluated by
204
+ * its value, a data object has to be passed in options.
205
+ *
206
+ * Per default this function will return `undefined` schema for valid properties
207
+ * that do not have a defined schema. Use the option `withSchemaWarning: true` to
208
+ * receive an error with `code: schema-warning` containing the location of its
209
+ * last evaluated json-schema.
210
+ *
211
+ * @returns { node } or { error } where node can also be undefined (valid but undefined)
212
+ */
198
213
  getNode(pointer: string, data: unknown, options: { withSchemaWarning: true } & GetNodeOptions): NodeOrError;
199
214
  getNode(pointer: string, data: unknown, options: { createSchema: true } & GetNodeOptions): NodeOrError;
200
215
  getNode(pointer: string, data?: unknown, options?: GetNodeOptions): OptionalNodeOrError;
201
216
 
202
- // getNodeChild overloads
217
+ /**
218
+ * Returns the child for the given property-name or array-index
219
+ *
220
+ * - the returned child node is **not reduced**
221
+ * - a child node $ref is resolved
222
+ *
223
+ * @returns { node } or { error } where node can also be undefined (valid but undefined)
224
+ */
203
225
  getNodeChild(
204
226
  key: string | number,
205
227
  data: unknown,
@@ -431,7 +453,7 @@ export const SchemaNodeMethods = {
431
453
  }
432
454
 
433
455
  // remove dynamic properties of node
434
- workingNode.schema = omit(workingNode.schema, ...DYNAMIC_PROPERTIES);
456
+ workingNode.schema = omit(workingNode.schema, DECLARATOR_ONEOF, ...DYNAMIC_PROPERTIES);
435
457
  // @ts-expect-error string accessing schema props
436
458
  DYNAMIC_PROPERTIES.forEach((prop) => (workingNode[prop] = undefined));
437
459
  return { node: workingNode, error: undefined };
@@ -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,8 +1,9 @@
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
+ import { draftEditor } from "../draftEditor";
6
7
  const DECLARATOR_ONEOF = settings.DECLARATOR_ONEOF;
7
8
 
8
9
  describe("keyword : oneof : validate", () => {
@@ -38,7 +39,7 @@ describe("keyword : oneOf : reduce", () => {
38
39
  ]
39
40
  }).reduceNode(111);
40
41
 
41
- assert.deepEqual(node.schema, { type: "number", title: "A Number" });
42
+ assert.deepEqual(node?.schema, { type: "number", title: "A Number" });
42
43
  assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
43
44
  });
44
45
 
@@ -72,7 +73,7 @@ describe("keyword : oneOf : reduce", () => {
72
73
  oneOf: [{ required: ["a"], properties: { a: { type: "string" } } }, { $ref: "#/$defs/withData" }]
73
74
  }).reduceNode({ b: 111 });
74
75
 
75
- assert.deepEqual(node.schema, { required: ["b"], properties: { b: { type: "number" } } });
76
+ assert.deepEqual(node?.schema, { required: ["b"], properties: { b: { type: "number" } } });
76
77
  // @note that we override nested oneOfIndex
77
78
  assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
78
79
  });
@@ -83,7 +84,7 @@ describe("keyword : oneOf : reduce", () => {
83
84
  oneOf: [{ required: ["a"], properties: { a: false } }, { $ref: "#/$defs/withData" }]
84
85
  }).reduceNode({ b: 111 });
85
86
 
86
- assert.deepEqual(node.schema, { required: ["b"], properties: { b: true } });
87
+ assert.deepEqual(node?.schema, { required: ["b"], properties: { b: true } });
87
88
  assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
88
89
  });
89
90
 
@@ -101,7 +102,7 @@ describe("keyword : oneOf : reduce", () => {
101
102
  ]
102
103
  }).reduceNode({ title: 4 });
103
104
 
104
- assert.deepEqual(node.schema, { type: "object", properties: { title: { type: "number" } } });
105
+ assert.deepEqual(node?.schema, { type: "object", properties: { title: { type: "number" } } });
105
106
  assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
106
107
  });
107
108
 
@@ -119,7 +120,7 @@ describe("keyword : oneOf : reduce", () => {
119
120
  ]
120
121
  }).reduceNode({ title: 4, test: 2 });
121
122
 
122
- assert.deepEqual(node.schema, { type: "object", additionalProperties: { type: "number" } });
123
+ assert.deepEqual(node?.schema, { type: "object", additionalProperties: { type: "number" } });
123
124
  assert.equal(node.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
124
125
  });
125
126
  });
@@ -130,7 +131,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
130
131
  oneOf: [{ type: "string" }, { type: "number" }, { type: "object" }]
131
132
  });
132
133
  const res = reduceOneOfFuzzy({ node, data: 4, pointer: "#", path: [] });
133
- assert.deepEqual(res.schema, { type: "number" });
134
+ assert.deepEqual(res?.schema, { type: "number" });
134
135
  assert.equal(res.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
135
136
  });
136
137
 
@@ -142,7 +143,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
142
143
  ]
143
144
  });
144
145
  const res = reduceOneOfFuzzy({ node, data: "anasterixcame", pointer: "#", path: [] });
145
- assert.deepEqual(res.schema, { type: "string", pattern: "asterix" });
146
+ assert.deepEqual(res?.schema, { type: "string", pattern: "asterix" });
146
147
  assert.equal(res.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
147
148
  });
148
149
 
@@ -155,7 +156,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
155
156
  oneOf: [{ $ref: "#/definitions/a" }, { $ref: "#/definitions/b" }]
156
157
  });
157
158
  const res = reduceOneOfFuzzy({ node, data: "anasterixcame", pointer: "#", path: [] });
158
- assert.deepEqual(res.schema, { type: "string", pattern: "asterix" });
159
+ assert.deepEqual(res?.schema, { type: "string", pattern: "asterix" });
159
160
  assert.equal(res.oneOfIndex, 1, "should have exposed correct resolved oneOfIndex");
160
161
  });
161
162
 
@@ -169,7 +170,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
169
170
  ]
170
171
  });
171
172
  const res = reduceOneOfFuzzy({ node, data: { description: "..." }, pointer: "#", path: [] });
172
- assert.deepEqual(res.schema, {
173
+ assert.deepEqual(res?.schema, {
173
174
  type: "object",
174
175
  properties: { description: { type: "string" } }
175
176
  });
@@ -183,7 +184,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
183
184
  ]
184
185
  });
185
186
  const res = reduceOneOfFuzzy({ node, data: { title: "asterix" }, pointer: "#", path: [] });
186
- assert.deepEqual(res.schema, {
187
+ assert.deepEqual(res?.schema, {
187
188
  type: "object",
188
189
  properties: { title: { type: "string" } }
189
190
  });
@@ -199,7 +200,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
199
200
  ]
200
201
  });
201
202
  const res = reduceOneOfFuzzy({ node, data: { a: 0, b: 1 }, pointer: "#", path: [] });
202
- assert.deepEqual(res.schema, {
203
+ assert.deepEqual(res?.schema, {
203
204
  type: "object",
204
205
  properties: { a: t, b: t, c: t }
205
206
  });
@@ -216,7 +217,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
216
217
  ]
217
218
  });
218
219
  const res = reduceOneOfFuzzy({ node, data: { a: true, b: 1 }, pointer: "#", path: [] });
219
- assert.deepEqual(res.schema, {
220
+ assert.deepEqual(res?.schema, {
220
221
  type: "object",
221
222
  properties: { a: { type: "boolean" }, b: t, d: t }
222
223
  });
@@ -252,7 +253,7 @@ describe("keyword : oneof-fuzzy : reduce", () => {
252
253
  node,
253
254
  data: { type: "teaser", redirectUrl: "http://example.com/test/pay/article.html" }
254
255
  });
255
- assert.deepEqual(res.schema, {
256
+ assert.deepEqual(res?.schema, {
256
257
  type: "object",
257
258
  properties: {
258
259
  redirectUrl: { format: "url", type: "string" },
@@ -284,7 +285,7 @@ describe("keyword : oneof-property : reduce", () => {
284
285
  ]
285
286
  });
286
287
  const res = reduceOneOfDeclarator({ node, data: { name: "2", title: 123 }, pointer: "#", path: [] });
287
- assert.deepEqual(res.schema, {
288
+ assert.deepEqual(res?.schema, {
288
289
  type: "object",
289
290
  properties: {
290
291
  name: { type: "string", pattern: "^2$" },
@@ -318,7 +319,7 @@ describe("keyword : oneof-property : reduce", () => {
318
319
  pointer: "#",
319
320
  path: []
320
321
  });
321
- assert.deepEqual(res.schema, {
322
+ assert.deepEqual(res?.schema, {
322
323
  type: "object",
323
324
  properties: {
324
325
  name: { type: "string", pattern: "^2$" },
@@ -349,31 +350,188 @@ describe("keyword : oneof-property : reduce", () => {
349
350
  assert(isJsonError(res), "expected result to be an error");
350
351
  assert.deepEqual(res.code, "missing-one-of-property-error");
351
352
  });
352
- });
353
353
 
354
- describe("array", () => {
355
- it("should return an error if no oneOfProperty could be matched", () => {
354
+ it("should return correct reference", () => {
356
355
  const node = compileSchema({
357
- oneOf: [
358
- {
359
- type: "object",
360
- required: ["title"],
361
- properties: {
362
- title: {
363
- type: "string"
364
- }
365
- }
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" } }
366
364
  }
367
- ]
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
+ }
368
386
  });
369
387
  const res = reduceOneOfDeclarator({
370
388
  node,
371
- data: { name: "2", title: "not a number" },
389
+ data: { name: "first", title: "not a number" },
372
390
  pointer: "#",
373
391
  path: []
374
392
  });
375
- assert(isJsonError(res), "expected result to be an error");
376
- 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
+ });
377
397
  });
378
398
  });
399
+
400
+ describe("array", () => {
401
+ // TODO test access on array-item as oneOfProperty id (oneOfProperty: "0")
402
+ });
403
+ });
404
+
405
+ describe("keyword : oneof-fuzzy : validate", () => {
406
+ it("should return one-of-error oneOfProperty does not match", () => {
407
+ const node = compileSchema(
408
+ {
409
+ type: "array",
410
+ items: {
411
+ oneOfProperty: "id",
412
+ oneOf: [
413
+ {
414
+ type: "object",
415
+ required: ["id"],
416
+ properties: { id: { const: "one" } }
417
+ },
418
+ {
419
+ type: "object",
420
+ required: ["id"],
421
+ properties: { id: { const: "two" } }
422
+ }
423
+ ]
424
+ }
425
+ },
426
+ { drafts: [draftEditor] }
427
+ );
428
+
429
+ const { errors } = node.validate([{ id: "unknown" }]);
430
+
431
+ assert.equal(errors.length, 1);
432
+ assert.deepEqual(errors[0].code, "one-of-error");
433
+ });
434
+
435
+ it("should return validation errors of object identified by oneOfProperty", () => {
436
+ const node = compileSchema(
437
+ {
438
+ type: "array",
439
+ items: {
440
+ oneOfProperty: "id",
441
+ oneOf: [
442
+ {
443
+ type: "object",
444
+ required: ["id"],
445
+ properties: {
446
+ id: { const: "one" },
447
+ title: { type: "string" }
448
+ }
449
+ }
450
+ ]
451
+ }
452
+ },
453
+ { drafts: [draftEditor] }
454
+ );
455
+
456
+ const { errors } = node.validate([{ id: "one", title: 123 }]);
457
+
458
+ assert.equal(errors.length, 1);
459
+ assert.deepEqual(errors[0].code, "type-error");
460
+ });
461
+
462
+ // issue json-editor
463
+ it("should return unique-items error for failed oneOf item", () => {
464
+ const node = compileSchema(
465
+ {
466
+ type: "object",
467
+ required: ["main"],
468
+ properties: {
469
+ main: {
470
+ type: "array",
471
+ items: {
472
+ oneOfProperty: "type",
473
+ oneOf: [{ $ref: "#/$defs/parent" }]
474
+ }
475
+ }
476
+ },
477
+ $defs: {
478
+ parent: {
479
+ type: "object",
480
+ title: "Parent",
481
+ description:
482
+ "Adding a duplicate item to this list fails as uniqueItems=true in children. @todo correct error message",
483
+ required: ["type", "children"],
484
+ properties: {
485
+ type: {
486
+ options: { hidden: true },
487
+ type: "string",
488
+ const: "parent"
489
+ },
490
+ children: {
491
+ type: "array",
492
+ title: "Children",
493
+ uniqueItems: true,
494
+ items: {
495
+ oneOfProperty: "type",
496
+ oneOf: [
497
+ {
498
+ type: "object",
499
+ title: "Child: First",
500
+ required: ["type"],
501
+ properties: {
502
+ type: {
503
+ options: { hidden: true },
504
+ type: "string",
505
+ const: "one"
506
+ }
507
+ }
508
+ },
509
+ {
510
+ type: "object",
511
+ title: "Child: Second",
512
+ required: ["type"],
513
+ properties: {
514
+ type: {
515
+ options: { hidden: true },
516
+ type: "string",
517
+ const: "two"
518
+ }
519
+ }
520
+ }
521
+ ]
522
+ }
523
+ }
524
+ }
525
+ }
526
+ }
527
+ },
528
+ { drafts: [draftEditor] }
529
+ );
530
+ const { errors } = node.validate({
531
+ main: [{ type: "parent", children: [{ type: "one" }, { type: "one" }] }]
532
+ });
533
+ assert.equal(errors.length, 1);
534
+ assert.deepEqual(errors[0].data.pointer, "#/main/0/children/1");
535
+ assert.deepEqual(errors[0].code, "unique-items-error");
536
+ });
379
537
  });