Skip to content

Commit 3e4e441

Browse files
committed
refactor contains errorHandler
1 parent 41801ee commit 3e4e441

File tree

5 files changed

+177
-10
lines changed

5 files changed

+177
-10
lines changed

src/error-handlers/contains.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import * as Instance from "@hyperjump/json-schema/instance/experimental";
2+
import { getSchema } from "@hyperjump/json-schema/experimental";
3+
import * as Schema from "@hyperjump/browser";
4+
import * as JsonPointer from "@hyperjump/json-pointer";
25
import { getErrors } from "../error-handling.js";
36

47
/**
8+
* @import { ContainsConstraints } from "../localization.js"
59
* @import { ErrorHandler, ErrorObject, NormalizedOutput } from "../index.d.ts"
610
*/
711

@@ -11,8 +15,26 @@ const contains = async (normalizedErrors, instance, localization) => {
1115
const errors = [];
1216
if (normalizedErrors["https://json-schema.org/keyword/contains"]) {
1317
for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/contains"]) {
18+
const position = schemaLocation.lastIndexOf("/");
19+
const parentLocation = schemaLocation.slice(0, position);
20+
21+
/** @type ContainsConstraints */
22+
const containsConstraints = {};
23+
const minContainsLocation = JsonPointer.append("minContains", parentLocation);
24+
const minContainsNode = await getSchema(minContainsLocation);
25+
/** @type number */
26+
containsConstraints.minContains = Schema.value(minContainsNode) ?? 1;
27+
28+
const maxContainsLocation = JsonPointer.append("maxContains", parentLocation);
29+
const maxContainsNode = await getSchema(maxContainsLocation);
30+
/** @type number */
31+
const maxContains = Schema.value(maxContainsNode);
32+
if (maxContains !== undefined) {
33+
containsConstraints.maxContains = maxContains;
34+
}
35+
1436
errors.push({
15-
message: localization.getContainsErrorMessage(),
37+
message: localization.getContainsErrorMessage(containsConstraints),
1638
instanceLocation: Instance.uri(instance),
1739
schemaLocation: schemaLocation
1840
});

src/keyword-error-message.test.js

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,13 +1032,139 @@ describe("Error messages", async () => {
10321032
});
10331033

10341034
test("normalized output for a failing 'contains' keyword", async () => {
1035+
registerSchema({
1036+
$schema: "https://json-schema.org/draft/2020-12/schema",
1037+
contains: {
1038+
type: "number",
1039+
multipleOf: 2
1040+
}
1041+
}, schemaUri);
1042+
const instance = ["", 3, 5];
1043+
const output = {
1044+
valid: false,
1045+
errors: [
1046+
{
1047+
valid: false,
1048+
keywordLocation: "/contains",
1049+
instanceLocation: "#",
1050+
absoluteKeywordLocation: "https://example.com/main#/contains",
1051+
errors: [
1052+
{
1053+
valid: false,
1054+
instanceLocation: "#/0",
1055+
absoluteKeywordLocation: "https://example.com/main#/contains/type"
1056+
},
1057+
{
1058+
valid: false,
1059+
instanceLocation: "#/1",
1060+
absoluteKeywordLocation: "https://example.com/main#/contains/multipleOf"
1061+
},
1062+
{
1063+
valid: false,
1064+
instanceLocation: "#/2",
1065+
absoluteKeywordLocation: "https://example.com/main#/contains/multipleOf"
1066+
}
1067+
]
1068+
}
1069+
]
1070+
};
1071+
const result = await betterJsonSchemaErrors(output, schemaUri, instance);
1072+
expect(result.errors).to.eql([
1073+
{
1074+
instanceLocation: "#",
1075+
message: localization.getContainsErrorMessage({ minContains: 1 }),
1076+
schemaLocation: "https://example.com/main#/contains"
1077+
},
1078+
{
1079+
instanceLocation: "#/0",
1080+
message: localization.getTypeErrorMessage("number", "string"),
1081+
schemaLocation: "https://example.com/main#/contains/type"
1082+
},
1083+
{
1084+
instanceLocation: "#/1",
1085+
message: localization.getMultipleOfErrorMessage(2),
1086+
schemaLocation: "https://example.com/main#/contains/multipleOf"
1087+
},
1088+
{
1089+
instanceLocation: "#/2",
1090+
message: localization.getMultipleOfErrorMessage(2),
1091+
schemaLocation: "https://example.com/main#/contains/multipleOf"
1092+
}
1093+
]);
1094+
});
1095+
1096+
test("normalized output for a failing 'contains' keyword with only minContains", async () => {
1097+
registerSchema({
1098+
$schema: "https://json-schema.org/draft/2020-12/schema",
1099+
contains: {
1100+
type: "number",
1101+
multipleOf: 2
1102+
},
1103+
minContains: 2
1104+
}, schemaUri);
1105+
const instance = ["", 3, 5];
1106+
const output = {
1107+
valid: false,
1108+
errors: [
1109+
{
1110+
valid: false,
1111+
keywordLocation: "/contains",
1112+
instanceLocation: "#",
1113+
absoluteKeywordLocation: "https://example.com/main#/contains",
1114+
errors: [
1115+
{
1116+
valid: false,
1117+
instanceLocation: "#/0",
1118+
absoluteKeywordLocation: "https://example.com/main#/contains/type"
1119+
},
1120+
{
1121+
valid: false,
1122+
instanceLocation: "#/1",
1123+
absoluteKeywordLocation: "https://example.com/main#/contains/multipleOf"
1124+
},
1125+
{
1126+
valid: false,
1127+
instanceLocation: "#/2",
1128+
absoluteKeywordLocation: "https://example.com/main#/contains/multipleOf"
1129+
}
1130+
]
1131+
}
1132+
]
1133+
};
1134+
const result = await betterJsonSchemaErrors(output, schemaUri, instance);
1135+
expect(result.errors).to.eql([
1136+
{
1137+
instanceLocation: "#",
1138+
message: localization.getContainsErrorMessage({ minContains: 2 }),
1139+
schemaLocation: "https://example.com/main#/contains"
1140+
},
1141+
{
1142+
instanceLocation: "#/0",
1143+
message: localization.getTypeErrorMessage("number", "string"),
1144+
schemaLocation: "https://example.com/main#/contains/type"
1145+
},
1146+
{
1147+
instanceLocation: "#/1",
1148+
message: localization.getMultipleOfErrorMessage(2),
1149+
schemaLocation: "https://example.com/main#/contains/multipleOf"
1150+
},
1151+
{
1152+
instanceLocation: "#/2",
1153+
message: localization.getMultipleOfErrorMessage(2),
1154+
schemaLocation: "https://example.com/main#/contains/multipleOf"
1155+
}
1156+
]);
1157+
});
1158+
1159+
test("`contains` with `minContains` and `maxContains` keyword", async () => {
10351160
registerSchema({
10361161
$schema: "https://json-schema.org/draft/2020-12/schema",
10371162
contains: {
10381163
type: "number",
10391164
multipleOf: 2
10401165
},
1041-
minContains: 1
1166+
minContains: 2,
1167+
maxContains: 4
10421168
}, schemaUri);
10431169
const instance = ["", 3, 5];
10441170
const output = {
@@ -1073,7 +1199,7 @@ describe("Error messages", async () => {
10731199
expect(result.errors).to.eql([
10741200
{
10751201
instanceLocation: "#",
1076-
message: localization.getContainsErrorMessage(),
1202+
message: localization.getContainsErrorMessage({ minContains: 2, maxContains: 4 }),
10771203
schemaLocation: "https://example.com/main#/contains"
10781204
},
10791205
{

src/localization.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ import { FluentBundle, FluentResource } from "@fluent/bundle";
2222
* }} StringConstraints
2323
*/
2424

25+
/**
26+
* @typedef {{
27+
* maxContains?: number;
28+
* minContains: number;
29+
* }} ContainsConstraints
30+
*/
31+
2532
export class Localization {
2633
/**
2734
* @param {string} locale
@@ -169,9 +176,13 @@ export class Localization {
169176
return this._formatMessage("format-error", { format });
170177
}
171178

172-
/** @type () => string */
173-
getContainsErrorMessage() {
174-
return this._formatMessage("contains-error");
179+
/** @type (constraints: ContainsConstraints) => string */
180+
getContainsErrorMessage(constraints) {
181+
if (constraints.maxContains) {
182+
return this._formatMessage("contains-error-min-max", constraints);
183+
} else {
184+
return this._formatMessage("contains-error-min", constraints);
185+
}
175186
}
176187

177188
/** @type () => string */

src/normalization-handlers/contains.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { evaluateSchema } from "../normalized-output.js";
22
import * as Instance from "@hyperjump/json-schema/instance/experimental";
3-
43
/**
54
* @import { KeywordHandler, NormalizedOutput } from "../index.d.ts"
65
* @import { EvaluatedItemsContext } from "./unevaluatedItems.js"

src/translations/en-US.ftl

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
type-error = The instance should be of type {$expected} but found {$actual}.
22
33
string-error = Expected a string {$constraints}.
4-
string-error-minLength = atleast {$minLength} characters long
5-
string-error-maxLength = atmost {$maxLength} characters long
4+
string-error-minLength = at least {$minLength} characters long
5+
string-error-maxLength = at most {$maxLength} characters long
66
string-error-pattern = to match the pattern {$pattern}
77
88
number-error = Expected a number {$constraints}.
@@ -20,7 +20,16 @@ max-items-error = The instance should contain a maximum of {$limit} items in the
2020
min-items-error = The instance should contain a minimum of {$limit} items in the array.
2121
unique-items-error = The instance should have unique items in the array.
2222
format-error = The instance should match the format: {$format}.
23-
contains-error = A required value is missing from the list.
23+
24+
contains-error-min = The array must contain at least {$minContains ->
25+
[one] item that passes
26+
*[other] items that pass
27+
} the 'contains' schema.
28+
contains-error-min-max = The array must contain at least {$minContains} and at most {$maxContains ->
29+
[one] item that passes
30+
*[other] items that pass
31+
} the 'contains' schema.
32+
2433
not-error = The instance is not allowed to be used in this schema.
2534
additional-properties-error = The property "{$propertyName}" is not allowed.
2635
dependent-required-error = Property "{$property}" requires property(s): {$missingDependents}.

0 commit comments

Comments
 (0)