json-schema-library 10.5.4 → 11.0.1

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.
Files changed (39) hide show
  1. package/README.md +128 -14
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.cts +42 -19
  4. package/dist/index.d.mts +42 -19
  5. package/dist/index.mjs +1 -1
  6. package/dist/jlib.js +2 -2
  7. package/index.ts +8 -4
  8. package/package.json +6 -6
  9. package/src/Keyword.ts +8 -3
  10. package/src/SchemaNode.ts +46 -10
  11. package/src/compileSchema.validate.test.ts +89 -54
  12. package/src/draft04.ts +10 -8
  13. package/src/draft06.ts +10 -8
  14. package/src/draft07.ts +10 -8
  15. package/src/draft2019-09/keywords/additionalItems.ts +2 -2
  16. package/src/draft2019-09/keywords/items.ts +2 -2
  17. package/src/draft2019-09/keywords/unevaluatedItems.ts +12 -6
  18. package/src/draft2019.ts +10 -8
  19. package/src/draft2020.ts +8 -6
  20. package/src/errors/errors.ts +3 -1
  21. package/src/formats/formats.ts +2 -6
  22. package/src/keywords/additionalProperties.ts +2 -2
  23. package/src/keywords/allOf.ts +2 -2
  24. package/src/keywords/dependencies.ts +5 -6
  25. package/src/keywords/dependentRequired.ts +2 -2
  26. package/src/keywords/dependentSchemas.ts +4 -3
  27. package/src/keywords/deprecated.ts +18 -0
  28. package/src/keywords/items.ts +2 -2
  29. package/src/keywords/oneOf.test.ts +150 -15
  30. package/src/keywords/oneOf.ts +64 -4
  31. package/src/keywords/patternProperties.ts +2 -2
  32. package/src/keywords/prefixItems.ts +2 -11
  33. package/src/keywords/properties.ts +2 -2
  34. package/src/keywords/unevaluatedItems.ts +2 -2
  35. package/src/keywords/unevaluatedProperties.ts +2 -2
  36. package/src/methods/getData.test.ts +1779 -1781
  37. package/src/types.ts +32 -18
  38. package/src/utils/sanitizeErrors.ts +9 -8
  39. package/src/validateNode.ts +2 -2
package/src/draft2019.ts CHANGED
@@ -1,17 +1,24 @@
1
+ import { $defsKeyword } from "./keywords/$defs";
2
+ import { $refKeyword } from "./draft2019-09/keywords/$ref";
1
3
  import { additionalItemsKeyword } from "./draft2019-09/keywords/additionalItems";
2
4
  import { additionalPropertiesKeyword } from "./keywords/additionalProperties";
3
5
  import { allOfKeyword } from "./keywords/allOf";
4
6
  import { anyOfKeyword } from "./keywords/anyOf";
5
7
  import { constKeyword } from "./keywords/const";
6
8
  import { containsKeyword } from "./keywords/contains";
7
- import { $defsKeyword } from "./keywords/$defs";
9
+ import { createSchema } from "./methods/createSchema";
8
10
  import { dependenciesKeyword } from "./keywords/dependencies";
9
11
  import { dependentRequiredKeyword } from "./keywords/dependentRequired";
10
12
  import { dependentSchemasKeyword } from "./keywords/dependentSchemas";
13
+ import { deprecatedKeyword } from "./keywords/deprecated";
11
14
  import { enumKeyword } from "./keywords/enum";
15
+ import { errors } from "./errors/errors";
12
16
  import { exclusiveMaximumKeyword } from "./keywords/exclusiveMaximum";
13
17
  import { exclusiveMinimumKeyword } from "./keywords/exclusiveMinimum";
14
18
  import { formatKeyword } from "./keywords/format";
19
+ import { formats } from "./formats/formats";
20
+ import { getChildSelection } from "./draft2019-09/methods/getChildSelection";
21
+ import { getData } from "./draft2019-09/methods/getData";
15
22
  import { ifKeyword } from "./keywords/ifthenelse";
16
23
  import { itemsKeyword } from "./draft2019-09/keywords/items";
17
24
  import { maximumKeyword } from "./keywords/maximum";
@@ -29,19 +36,13 @@ import { patternKeyword } from "./keywords/pattern";
29
36
  import { patternPropertiesKeyword } from "./keywords/patternProperties";
30
37
  import { propertiesKeyword } from "./keywords/properties";
31
38
  import { propertyNamesKeyword } from "./keywords/propertyNames";
32
- import { $refKeyword } from "./draft2019-09/keywords/$ref";
33
39
  import { requiredKeyword } from "./keywords/required";
34
40
  import { sanitizeKeywords } from "./Draft";
41
+ import { toDataNodes } from "./methods/toDataNodes";
35
42
  import { typeKeyword } from "./keywords/type";
36
43
  import { unevaluatedItemsKeyword } from "./draft2019-09/keywords/unevaluatedItems";
37
44
  import { unevaluatedPropertiesKeyword } from "./keywords/unevaluatedProperties";
38
45
  import { uniqueItemsKeyword } from "./keywords/uniqueItems";
39
- import { getChildSelection } from "./draft2019-09/methods/getChildSelection";
40
- import { getData } from "./draft2019-09/methods/getData";
41
- import { toDataNodes } from "./methods/toDataNodes";
42
- import { createSchema } from "./methods/createSchema";
43
- import { errors } from "./errors/errors";
44
- import { formats } from "./formats/formats";
45
46
 
46
47
  /**
47
48
  * @draft-2019 https://json-schema.org/draft/2019-09/release-notes
@@ -79,6 +80,7 @@ export const draft2019 = sanitizeKeywords({
79
80
  dependenciesKeyword, // optional support for old draft-version
80
81
  dependentRequiredKeyword, // draft-2019: new
81
82
  dependentSchemasKeyword, // draft-2019: new
83
+ deprecatedKeyword,
82
84
  enumKeyword,
83
85
  exclusiveMaximumKeyword,
84
86
  exclusiveMinimumKeyword,
package/src/draft2020.ts CHANGED
@@ -1,17 +1,21 @@
1
+ import { $defsKeyword } from "./keywords/$defs";
2
+ import { $refKeyword } from "./keywords/$ref";
1
3
  import { additionalPropertiesKeyword } from "./keywords/additionalProperties";
2
4
  import { allOfKeyword } from "./keywords/allOf";
3
5
  import { anyOfKeyword } from "./keywords/anyOf";
4
6
  import { constKeyword } from "./keywords/const";
5
7
  import { containsKeyword } from "./keywords/contains";
6
- import { $defsKeyword } from "./keywords/$defs";
8
+ import { createSchema } from "./methods/createSchema";
7
9
  import { dependenciesKeyword } from "./keywords/dependencies";
8
10
  import { dependentRequiredKeyword } from "./keywords/dependentRequired";
9
11
  import { dependentSchemasKeyword } from "./keywords/dependentSchemas";
10
- import { toDataNodes } from "./methods/toDataNodes";
12
+ import { deprecatedKeyword } from "./keywords/deprecated";
11
13
  import { enumKeyword } from "./keywords/enum";
14
+ import { errors } from "./errors/errors";
12
15
  import { exclusiveMaximumKeyword } from "./keywords/exclusiveMaximum";
13
16
  import { exclusiveMinimumKeyword } from "./keywords/exclusiveMinimum";
14
17
  import { formatKeyword } from "./keywords/format";
18
+ import { formats } from "./formats/formats";
15
19
  import { getChildSelection } from "./methods/getChildSelection";
16
20
  import { getData } from "./methods/getData";
17
21
  import { ifKeyword } from "./keywords/ifthenelse";
@@ -32,16 +36,13 @@ import { patternPropertiesKeyword } from "./keywords/patternProperties";
32
36
  import { prefixItemsKeyword } from "./keywords/prefixItems";
33
37
  import { propertiesKeyword } from "./keywords/properties";
34
38
  import { propertyNamesKeyword } from "./keywords/propertyNames";
35
- import { $refKeyword } from "./keywords/$ref";
36
39
  import { requiredKeyword } from "./keywords/required";
37
40
  import { sanitizeKeywords } from "./Draft";
41
+ import { toDataNodes } from "./methods/toDataNodes";
38
42
  import { typeKeyword } from "./keywords/type";
39
43
  import { unevaluatedItemsKeyword } from "./keywords/unevaluatedItems";
40
44
  import { unevaluatedPropertiesKeyword } from "./keywords/unevaluatedProperties";
41
45
  import { uniqueItemsKeyword } from "./keywords/uniqueItems";
42
- import { createSchema } from "./methods/createSchema";
43
- import { errors } from "./errors/errors";
44
- import { formats } from "./formats/formats";
45
46
 
46
47
  /**
47
48
  * @draft-2020-12 https://json-schema.org/draft/2020-12/release-notes
@@ -90,6 +91,7 @@ export const draft2020 = sanitizeKeywords({
90
91
  dependenciesKeyword,
91
92
  dependentRequiredKeyword,
92
93
  dependentSchemasKeyword,
94
+ deprecatedKeyword,
93
95
  enumKeyword,
94
96
  exclusiveMaximumKeyword,
95
97
  exclusiveMinimumKeyword,
@@ -71,5 +71,7 @@ export const errors = {
71
71
  "unique-items-error":
72
72
  "Items in array must be unique. Value `{{value}}` in `{{pointer}}` is a duplicate of {{duplicatePointer}}.",
73
73
  "unknown-property-error": "Could not find a valid schema for property `{{pointer}}` within object",
74
- "value-not-empty-error": "A value for `{{property}}` is required at `{{pointer}}`"
74
+ "value-not-empty-error": "A value for `{{property}}` is required at `{{pointer}}`",
75
+ // annotations
76
+ "deprecated-warning": "Value at `{{pointer}}` is deprecated"
75
77
  };
@@ -1,9 +1,8 @@
1
1
  /* eslint-disable no-control-regex */
2
2
  import { getTypeOf } from "../utils/getTypeOf";
3
3
  import validUrl from "valid-url";
4
- import { JsonSchemaValidatorParams, ValidationResult } from "../Keyword";
4
+ import { JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
5
5
  import { parse as parseIdnEmail } from "smtp-address-parser";
6
- import { JsonError } from "../types";
7
6
  import settings from "../settings";
8
7
 
9
8
  const { REGEX_FLAGS } = settings;
@@ -27,10 +26,7 @@ const isValidURITemplate =
27
26
  const isValidDurationString = /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?$/;
28
27
 
29
28
  // Default Json-Schema formats: date-time, email, hostname, ipv4, ipv6, uri, uriref
30
- export const formats: Record<
31
- string,
32
- (options: JsonSchemaValidatorParams) => undefined | JsonError | ValidationResult[]
33
- > = {
29
+ export const formats: Record<string, (options: JsonSchemaValidatorParams) => ValidationReturnType> = {
34
30
  date: ({ node, pointer, data }) => {
35
31
  const { schema } = node;
36
32
  if (typeof data !== "string" || data === "") {
@@ -1,6 +1,6 @@
1
1
  import settings from "../settings";
2
2
  import { isObject } from "../utils/isObject";
3
- import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationResult } from "../Keyword";
3
+ import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationReturnType} from "../Keyword";
4
4
  import { SchemaNode } from "../types";
5
5
  import { getValue } from "../utils/getValue";
6
6
  import { validateNode } from "../validateNode";
@@ -65,7 +65,7 @@ function validateAdditionalProperty({ node, data, pointer = "#", path }: JsonSch
65
65
  }
66
66
 
67
67
  const { schema } = node;
68
- const errors: ValidationResult[] = [];
68
+ const errors: ValidationReturnType = [];
69
69
  let receivedProperties = Object.keys(data).filter((prop) => settings.propertyBlacklist.includes(prop) === false);
70
70
  const patternProperties = node.patternProperties;
71
71
  if (Array.isArray(patternProperties)) {
@@ -1,5 +1,5 @@
1
1
  import { mergeSchema } from "../utils/mergeSchema";
2
- import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationResult } from "../Keyword";
2
+ import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
3
3
  import { SchemaNode } from "../types";
4
4
  import { validateNode } from "../validateNode";
5
5
 
@@ -55,7 +55,7 @@ function validateAllOf({ node, data, pointer, path }: JsonSchemaValidatorParams)
55
55
  if (!Array.isArray(node.allOf) || node.allOf.length === 0) {
56
56
  return;
57
57
  }
58
- const errors: ValidationResult[] = [];
58
+ const errors: ValidationReturnType = [];
59
59
  node.allOf.forEach((allOfNode) => {
60
60
  errors.push(...validateNode(allOfNode, data, pointer, path));
61
61
  });
@@ -1,10 +1,11 @@
1
1
  import { isSchemaNode, SchemaNode } from "../types";
2
- import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationResult } from "../Keyword";
2
+ import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
3
3
  import { isObject } from "../utils/isObject";
4
4
  import { mergeNode } from "../mergeNode";
5
5
  import { hasProperty } from "../utils/hasProperty";
6
6
  import { validateDependentRequired } from "./dependentRequired";
7
7
  import { validateDependentSchemas } from "./dependentSchemas";
8
+ import sanitizeErrors from "../utils/sanitizeErrors";
8
9
 
9
10
  export const dependenciesKeyword: Keyword = {
10
11
  id: "dependencies",
@@ -125,15 +126,13 @@ function validateDependencies({ node, data, pointer, path }: JsonSchemaValidator
125
126
  if (!isObject(data)) {
126
127
  return undefined;
127
128
  }
128
- let errors: ValidationResult[] = [];
129
+ const errors: ValidationAnnotation[] = [];
129
130
  if (node.dependentRequired) {
130
- errors = validateDependentRequired({ node, data, pointer, path }) ?? [];
131
+ sanitizeErrors(validateDependentRequired({ node, data, pointer, path }), errors);
131
132
  }
132
133
  if (node.dependentSchemas) {
133
134
  const schemaErrors = validateDependentSchemas({ node, data, pointer, path });
134
- if (schemaErrors) {
135
- errors.push(...schemaErrors);
136
- }
135
+ sanitizeErrors(schemaErrors, errors);
137
136
  }
138
137
  return errors;
139
138
  }
@@ -1,4 +1,4 @@
1
- import { Keyword, JsonSchemaValidatorParams, ValidationResult } from "../Keyword";
1
+ import { Keyword, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
2
2
  import { JsonError, SchemaNode } from "../types";
3
3
  import { isObject } from "../utils/isObject";
4
4
 
@@ -21,7 +21,7 @@ export function validateDependentRequired({
21
21
  node,
22
22
  data,
23
23
  pointer = "#"
24
- }: JsonSchemaValidatorParams): undefined | ValidationResult[] {
24
+ }: JsonSchemaValidatorParams): ValidationReturnType {
25
25
  if (!isObject(data)) {
26
26
  return undefined;
27
27
  }
@@ -1,8 +1,9 @@
1
1
  import { mergeSchema } from "../utils/mergeSchema";
2
2
  import { isObject } from "../utils/isObject";
3
3
  import { isSchemaNode, SchemaNode, JsonSchema } from "../types";
4
- import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationResult } from "../Keyword";
4
+ import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
5
5
  import { validateNode } from "../validateNode";
6
+ import sanitizeErrors from "../utils/sanitizeErrors";
6
7
 
7
8
  export const dependentSchemasKeyword: Keyword = {
8
9
  id: "dependentSchemas",
@@ -78,7 +79,7 @@ export function validateDependentSchemas({ node, data, pointer, path }: JsonSche
78
79
  if (!isObject(data) || dependentSchemas == null) {
79
80
  return undefined;
80
81
  }
81
- const errors: ValidationResult[] = [];
82
+ const errors: ValidationAnnotation[] = [];
82
83
  Object.keys(data).forEach((property) => {
83
84
  const dependencies = dependentSchemas[property];
84
85
  // @draft >= 6 boolean schema
@@ -90,7 +91,7 @@ export function validateDependentSchemas({ node, data, pointer, path }: JsonSche
90
91
  return;
91
92
  }
92
93
  if (isSchemaNode(dependencies)) {
93
- errors.push(...validateNode(dependencies, data, pointer, path));
94
+ sanitizeErrors(validateNode(dependencies, data, pointer, path), errors);
94
95
  return;
95
96
  }
96
97
  });
@@ -0,0 +1,18 @@
1
+ import { Keyword, JsonSchemaValidatorParams } from "../Keyword";
2
+
3
+ export const deprecatedKeyword: Keyword = {
4
+ id: "deprecated",
5
+ keyword: "deprecated",
6
+ addValidate: ({ schema }) => schema.deprecated === true,
7
+ validate: validateDeprecated
8
+ };
9
+
10
+ function validateDeprecated({ node, data, pointer }: JsonSchemaValidatorParams) {
11
+ return [
12
+ node.createAnnotation("deprecated-warning", {
13
+ pointer,
14
+ schema: node.schema,
15
+ value: data
16
+ })
17
+ ];
18
+ }
@@ -1,4 +1,4 @@
1
- import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationResult } from "../Keyword";
1
+ import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
2
2
  import { SchemaNode } from "../types";
3
3
  import { isObject } from "../utils/isObject";
4
4
  import { validateNode } from "../validateNode";
@@ -52,7 +52,7 @@ function validateItems({ node, data, pointer = "#", path }: JsonSchemaValidatorP
52
52
  return node.createError("invalid-data-error", { pointer, value: data, schema });
53
53
  }
54
54
 
55
- const errors: ValidationResult[] = [];
55
+ const errors: ValidationReturnType = [];
56
56
  if (node.items) {
57
57
  for (let i = schema.prefixItems?.length ?? 0; i < data.length; i += 1) {
58
58
  const itemData = data[i];
@@ -3,6 +3,7 @@ import { compileSchema } from "../compileSchema";
3
3
  import { isJsonError } 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$" },
@@ -377,3 +378,137 @@ describe("keyword : oneof-property : reduce", () => {
377
378
  });
378
379
  });
379
380
  });
381
+
382
+ describe("keyword : oneof-fuzzy : validate", () => {
383
+ it("should return one-of-error oneOfProperty does not match", () => {
384
+ const node = compileSchema(
385
+ {
386
+ type: "array",
387
+ items: {
388
+ oneOfProperty: "id",
389
+ oneOf: [
390
+ {
391
+ type: "object",
392
+ required: ["id"],
393
+ properties: { id: { const: "one" } }
394
+ },
395
+ {
396
+ type: "object",
397
+ required: ["id"],
398
+ properties: { id: { const: "two" } }
399
+ }
400
+ ]
401
+ }
402
+ },
403
+ { drafts: [draftEditor] }
404
+ );
405
+
406
+ const { errors } = node.validate([{ id: "unknown" }]);
407
+
408
+ assert.equal(errors.length, 1);
409
+ assert.deepEqual(errors[0].code, "one-of-error");
410
+ });
411
+
412
+ it("should return validation errors of object identified by oneOfProperty", () => {
413
+ const node = compileSchema(
414
+ {
415
+ type: "array",
416
+ items: {
417
+ oneOfProperty: "id",
418
+ oneOf: [
419
+ {
420
+ type: "object",
421
+ required: ["id"],
422
+ properties: {
423
+ id: { const: "one" },
424
+ title: { type: "string" }
425
+ }
426
+ }
427
+ ]
428
+ }
429
+ },
430
+ { drafts: [draftEditor] }
431
+ );
432
+
433
+ const { errors } = node.validate([{ id: "one", title: 123 }]);
434
+
435
+ assert.equal(errors.length, 1);
436
+ assert.deepEqual(errors[0].code, "type-error");
437
+ });
438
+
439
+ // issue json-editor
440
+ it("should return unique-items error for failed oneOf item", () => {
441
+ const node = compileSchema(
442
+ {
443
+ type: "object",
444
+ required: ["main"],
445
+ properties: {
446
+ main: {
447
+ type: "array",
448
+ items: {
449
+ oneOfProperty: "type",
450
+ oneOf: [{ $ref: "#/$defs/parent" }]
451
+ }
452
+ }
453
+ },
454
+ $defs: {
455
+ parent: {
456
+ type: "object",
457
+ title: "Parent",
458
+ description:
459
+ "Adding a duplicate item to this list fails as uniqueItems=true in children. @todo correct error message",
460
+ required: ["type", "children"],
461
+ properties: {
462
+ type: {
463
+ options: { hidden: true },
464
+ type: "string",
465
+ const: "parent"
466
+ },
467
+ children: {
468
+ type: "array",
469
+ title: "Children",
470
+ uniqueItems: true,
471
+ items: {
472
+ oneOfProperty: "type",
473
+ oneOf: [
474
+ {
475
+ type: "object",
476
+ title: "Child: First",
477
+ required: ["type"],
478
+ properties: {
479
+ type: {
480
+ options: { hidden: true },
481
+ type: "string",
482
+ const: "one"
483
+ }
484
+ }
485
+ },
486
+ {
487
+ type: "object",
488
+ title: "Child: Second",
489
+ required: ["type"],
490
+ properties: {
491
+ type: {
492
+ options: { hidden: true },
493
+ type: "string",
494
+ const: "two"
495
+ }
496
+ }
497
+ }
498
+ ]
499
+ }
500
+ }
501
+ }
502
+ }
503
+ }
504
+ },
505
+ { drafts: [draftEditor] }
506
+ );
507
+ const { errors } = node.validate({
508
+ main: [{ type: "parent", children: [{ type: "one" }, { type: "one" }] }]
509
+ });
510
+ assert.equal(errors.length, 1);
511
+ assert.deepEqual(errors[0].data.pointer, "#/main/0/children/1");
512
+ assert.deepEqual(errors[0].code, "unique-items-error");
513
+ });
514
+ });
@@ -3,7 +3,7 @@ import {
3
3
  JsonSchemaReducerParams,
4
4
  JsonSchemaValidatorParams,
5
5
  ValidationPath,
6
- ValidationResult
6
+ ValidationReturnType
7
7
  } from "../Keyword";
8
8
  import { isSchemaNode, SchemaNode } from "../types";
9
9
  import settings from "../settings";
@@ -57,7 +57,7 @@ function reduceOneOf({ node, data, pointer, path }: Omit<JsonSchemaReducerParams
57
57
  }
58
58
 
59
59
  const matches: { index: number; node: SchemaNode }[] = [];
60
- const errors: ValidationResult[] = [];
60
+ const errors: ValidationReturnType[] = [];
61
61
  for (let i = 0; i < node.oneOf.length; i += 1) {
62
62
  const validationErrors = validateNode(node.oneOf[i], data, pointer, path);
63
63
  if (validationErrors.length === 0) {
@@ -106,7 +106,7 @@ export function reduceOneOfDeclarator({ node, data, pointer, path }: Omit<JsonSc
106
106
  return;
107
107
  }
108
108
 
109
- const errors: ValidationResult[] = [];
109
+ const errors: ValidationReturnType = [];
110
110
  const oneOfProperty = node.schema[DECLARATOR_ONEOF];
111
111
  const oneOfValue = getValue(data, oneOfProperty);
112
112
 
@@ -236,13 +236,73 @@ export function reduceOneOfFuzzy({ node, data, pointer, path }: Omit<JsonSchemaR
236
236
  return oneOfResult;
237
237
  }
238
238
 
239
+ function validateFromDeclarator({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) {
240
+ const { oneOf, schema } = node;
241
+ if (!oneOf) {
242
+ return;
243
+ }
244
+
245
+ // with a declarator we only validate by a declarator to retrieve matches.
246
+ // - if a single match was found, we return validation errors if any
247
+ // - if no match was found we return a one-of-error
248
+ // - if multiples matches were found we return a multiple-one-of-error
249
+ const oneOfProperty = schema[DECLARATOR_ONEOF];
250
+ const oneOfValue = getValue(data, oneOfProperty);
251
+ const matches: { index: number; node: SchemaNode }[] = [];
252
+ const errors: ValidationReturnType = [];
253
+ for (const oneOfNode of oneOf) {
254
+ const { node: oneOfPropertyNode, error } = oneOfNode.getNodeChild(oneOfProperty, oneOfValue);
255
+ if (oneOfPropertyNode) {
256
+ const validationResult = validateNode(oneOfPropertyNode, oneOfValue, `${pointer}/${oneOfProperty}`, path);
257
+ if (validationResult.length > 0) {
258
+ errors.push(...validationResult);
259
+ } else {
260
+ matches.push({ index: oneOf.indexOf(oneOfNode), node: oneOfNode });
261
+ }
262
+ } else {
263
+ console.log(
264
+ `jlib oneOf error: failed getting schema for '${oneOfProperty}' to resolve ${DECLARATOR_ONEOF} in ${pointer}/oneOf/${oneOf.indexOf(oneOfNode)}`,
265
+ error
266
+ );
267
+ }
268
+ }
269
+
270
+ if (matches.length === 1) {
271
+ const match = matches[0];
272
+ match.node.oneOfIndex = match.index; // @evaluation-info
273
+ return validateNode(match.node, data, pointer, path);
274
+ }
275
+
276
+ if (matches.length > 1) {
277
+ return node.createError("multiple-one-of-error", {
278
+ value: data,
279
+ pointer,
280
+ schema,
281
+ matches
282
+ });
283
+ }
284
+
285
+ return node.createError("one-of-error", {
286
+ value: JSON.stringify(data),
287
+ pointer,
288
+ schema,
289
+ oneOf: schema.oneOf,
290
+ errors
291
+ });
292
+ }
293
+
239
294
  function oneOfValidator({ node, data, pointer = "#", path }: JsonSchemaValidatorParams) {
240
295
  const { oneOf, schema } = node;
241
296
  if (!oneOf) {
242
297
  return;
243
298
  }
299
+
300
+ if (schema[DECLARATOR_ONEOF]) {
301
+ return validateFromDeclarator({ node, data, pointer, path });
302
+ }
303
+
244
304
  const matches: { index: number; node: SchemaNode }[] = [];
245
- const errors: ValidationResult[] = [];
305
+ const errors: ValidationReturnType = [];
246
306
  for (let i = 0; i < oneOf.length; i += 1) {
247
307
  const validationResult = validateNode(oneOf[i], data, pointer, path);
248
308
  if (validationResult.length > 0) {