Skip to content

Commit 13babea

Browse files
Trimming alternatives for anyOf/oneOf based on type (#170)
* feature trimming alternatives for anyOf/oneOf based on type * add newline at end of oneOf.json Add a newline at the end of the JSON file * add newline at end of anyOf.json Add missing newline at the end of anyOf.json * use normalizedErrors for anyOf/oneOf type filtering * cleanup anyOf/oneOf error handler
1 parent dca15b6 commit 13babea

4 files changed

Lines changed: 409 additions & 20 deletions

File tree

src/error-handlers/anyOf.js

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,33 @@ const anyOfErrorHandler = async (normalizedErrors, instance, localization) => {
1717
}
1818

1919
const alternatives = [];
20+
const instanceLocation = Instance.uri(instance);
21+
2022
for (const alternative of anyOf) {
21-
alternatives.push(await getErrors(alternative, instance, localization));
23+
const typeErrors = alternative[instanceLocation]["https://json-schema.org/keyword/type"];
24+
const match = !typeErrors || Object.values(typeErrors).every((isValid) => isValid);
25+
26+
if (match) {
27+
alternatives.push(await getErrors(alternative, instance, localization));
28+
}
2229
}
2330

24-
errors.push({
25-
message: localization.getAnyOfErrorMessage(),
26-
alternatives: alternatives,
27-
instanceLocation: Instance.uri(instance),
28-
schemaLocations: [schemaLocation]
29-
});
31+
if (alternatives.length === 0) {
32+
for (const alternative of anyOf) {
33+
alternatives.push(await getErrors(alternative, instance, localization));
34+
}
35+
}
36+
37+
if (alternatives.length === 1) {
38+
errors.push(...alternatives[0]);
39+
} else {
40+
errors.push({
41+
message: localization.getAnyOfErrorMessage(),
42+
alternatives: alternatives,
43+
instanceLocation: Instance.uri(instance),
44+
schemaLocations: [schemaLocation]
45+
});
46+
}
3047
}
3148

3249
return errors;

src/error-handlers/oneOf.js

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,44 @@ const oneOfErrorHandler = async (normalizedErrors, instance, localization) => {
1717
}
1818

1919
const alternatives = [];
20+
const instanceLocation = Instance.uri(instance);
2021
let matchCount = 0;
22+
2123
for (const alternative of oneOf) {
22-
const alternativeErrors = await getErrors(alternative, instance, localization);
23-
if (alternativeErrors.length) {
24+
const typeErrors = alternative[instanceLocation]["https://json-schema.org/keyword/type"];
25+
const match = !typeErrors || Object.values(typeErrors).every((isValid) => isValid);
26+
27+
if (match) {
28+
const alternativeErrors = await getErrors(alternative, instance, localization);
29+
if (alternativeErrors.length) {
30+
alternatives.push(alternativeErrors);
31+
} else {
32+
matchCount++;
33+
}
34+
}
35+
}
36+
37+
if (matchCount === 0 && alternatives.length === 0) {
38+
for (const alternative of oneOf) {
39+
const alternativeErrors = await getErrors(alternative, instance, localization);
2440
alternatives.push(alternativeErrors);
25-
} else {
26-
matchCount++;
2741
}
2842
}
2943

30-
/** @type ErrorObject */
31-
const alternativeErrors = {
32-
message: localization.getOneOfErrorMessage(matchCount),
33-
instanceLocation: Instance.uri(instance),
34-
schemaLocations: [schemaLocation]
35-
};
36-
if (alternatives.length) {
37-
alternativeErrors.alternatives = alternatives;
44+
if (alternatives.length === 1 && matchCount === 0) {
45+
errors.push(...alternatives[0]);
46+
} else {
47+
/** @type ErrorObject */
48+
const alternativeErrors = {
49+
message: localization.getOneOfErrorMessage(matchCount),
50+
instanceLocation: Instance.uri(instance),
51+
schemaLocations: [schemaLocation]
52+
};
53+
if (alternatives.length) {
54+
alternativeErrors.alternatives = alternatives;
55+
}
56+
errors.push(alternativeErrors);
3857
}
39-
errors.push(alternativeErrors);
4058
}
4159

4260
return errors;

src/test-suite/tests/anyOf.json

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,161 @@
5252
},
5353
"instance": 42,
5454
"errors": []
55+
},
56+
{
57+
"description": "anyOf single type match flattens errors (removes anyOf error message)",
58+
"schema": {
59+
"anyOf": [
60+
{
61+
"type": "string",
62+
"maxLength": 2
63+
},
64+
{
65+
"type": "number",
66+
"minimum": 2
67+
}
68+
]
69+
},
70+
"instance": "foo",
71+
"errors": [
72+
{
73+
"messageId": "maxLength-message",
74+
"messageParams": {
75+
"maxLength": 2
76+
},
77+
"instanceLocation": "#",
78+
"schemaLocations": ["#/anyOf/0/maxLength"]
79+
}
80+
]
81+
},
82+
{
83+
"description": "none of the alternatives match instance type, hence no filtering of alternatives",
84+
"schema": {
85+
"anyOf": [
86+
{
87+
"type": "string",
88+
"maxLength": 2
89+
},
90+
{
91+
"type": "number",
92+
"minimum": 2
93+
}
94+
]
95+
},
96+
"instance": true,
97+
"errors": [
98+
{
99+
"messageId": "anyOf-message",
100+
"alternatives": [
101+
[
102+
{
103+
"messageId": "type-message",
104+
"messageParams": {
105+
"expectedTypes": { "or": ["string"] }
106+
},
107+
"instanceLocation": "#",
108+
"schemaLocations": ["#/anyOf/0/type"]
109+
}
110+
],
111+
[
112+
{
113+
"messageId": "type-message",
114+
"messageParams": {
115+
"expectedTypes": { "or": ["number"] }
116+
},
117+
"instanceLocation": "#",
118+
"schemaLocations": ["#/anyOf/1/type"]
119+
}
120+
]
121+
],
122+
"instanceLocation": "#",
123+
"schemaLocations": ["#/anyOf"]
124+
}
125+
]
126+
},
127+
{
128+
"description": "anyOf with multiple matching types does not collapse distinct errors",
129+
"schema": {
130+
"anyOf": [
131+
{
132+
"type": "string",
133+
"pattern": "^[0-9]+$"
134+
},
135+
{
136+
"type": "string",
137+
"format": "email"
138+
},
139+
{
140+
"type": "number",
141+
"minimum": 2
142+
}
143+
]
144+
},
145+
"instance": "hello",
146+
"errors": [
147+
{
148+
"messageId": "anyOf-message",
149+
"alternatives": [
150+
[
151+
{
152+
"messageId": "pattern-message",
153+
"messageParams": {
154+
"pattern": "^[0-9]+$"
155+
},
156+
"instanceLocation": "#",
157+
"schemaLocations": ["#/anyOf/0/pattern"]
158+
}
159+
],
160+
[
161+
{
162+
"messageId": "format-message",
163+
"messageParams": {
164+
"format": "email"
165+
},
166+
"instanceLocation": "#",
167+
"schemaLocations": ["#/anyOf/1/format"]
168+
}
169+
]
170+
],
171+
"instanceLocation": "#",
172+
"schemaLocations": ["#/anyOf"]
173+
}
174+
]
175+
},
176+
{
177+
"description": "anyOf with $ref alternatives filters by type",
178+
"compatibility": "2019",
179+
"schema": {
180+
"$defs": {
181+
"bar": {
182+
"type": "string",
183+
"maxLength": 2
184+
},
185+
"baz": {
186+
"type": "number",
187+
"minimum": 2
188+
}
189+
},
190+
"anyOf": [
191+
{
192+
"$ref": "#/$defs/bar"
193+
},
194+
{
195+
"$ref": "#/$defs/baz"
196+
}
197+
]
198+
},
199+
"instance": "foo",
200+
"errors": [
201+
{
202+
"messageId": "maxLength-message",
203+
"messageParams": {
204+
"maxLength": 2
205+
},
206+
"instanceLocation": "#",
207+
"schemaLocations": ["#/$defs/bar/maxLength"]
208+
}
209+
]
55210
}
56211
]
57212
}

0 commit comments

Comments
 (0)