Skip to content

Commit a1edde2

Browse files
authored
Merge pull request #45 from arpitkuriyal/starNotation
Star notation
2 parents 1c784fc + 0bba4dc commit a1edde2

File tree

3 files changed

+115
-69
lines changed

3 files changed

+115
-69
lines changed

src/keywordErrorMessage.test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,60 @@ describe("Error messages", async () => {
10811081
]
10821082
};
10831083

1084+
const result = await betterJsonSchemaErrors(instance, errorOutput, schemaUri);
1085+
expect(result.errors).to.eql([
1086+
{
1087+
instanceLocation: "#/Foo",
1088+
message: localization.getPatternErrorMessage("^[a-z]*$"),
1089+
schemaLocation: "https://example.com/main#/propertyNames/pattern"
1090+
}
1091+
]);
1092+
});
1093+
1094+
test("propertyName case -- without star notation", async () => {
1095+
registerSchema({
1096+
$schema: "https://json-schema.org/draft/2020-12/schema",
1097+
propertyNames: { pattern: "^[a-z]*$" }
1098+
}, schemaUri);
1099+
const instance = { Foo: 1 };
1100+
const errorOutput = {
1101+
valid: false,
1102+
errors: [
1103+
{
1104+
valid: false,
1105+
absoluteKeywordLocation: "https://example.com/main#/propertyNames/pattern",
1106+
instanceLocation: "#/Foo"
1107+
}
1108+
]
1109+
};
1110+
1111+
const result = await betterJsonSchemaErrors(instance, errorOutput, schemaUri);
1112+
expect(result.errors).to.eql([
1113+
{
1114+
instanceLocation: "#/Foo",
1115+
message: localization.getPatternErrorMessage("^[a-z]*$"),
1116+
schemaLocation: "https://example.com/main#/propertyNames/pattern"
1117+
}
1118+
]);
1119+
});
1120+
1121+
test.skip("propertyName case -- error on the object, not the key", async () => {
1122+
registerSchema({
1123+
$schema: "https://json-schema.org/draft/2020-12/schema",
1124+
propertyNames: { pattern: "^[a-z]*$" }
1125+
}, schemaUri);
1126+
const instance = { Foo: 1 };
1127+
const errorOutput = {
1128+
valid: false,
1129+
errors: [
1130+
{
1131+
valid: false,
1132+
absoluteKeywordLocation: "https://example.com/main#/propertyNames",
1133+
instanceLocation: "#"
1134+
}
1135+
]
1136+
};
1137+
10841138
const result = await betterJsonSchemaErrors(instance, errorOutput, schemaUri);
10851139
expect(result.errors).to.eql([
10861140
{
@@ -1090,6 +1144,7 @@ describe("Error messages", async () => {
10901144
}
10911145
]);
10921146
});
1147+
10931148
test("should fail when an additional property is found and additionalProperties is false", async () => {
10941149
registerSchema({
10951150
$schema: "https://json-schema.org/draft/2020-12/schema",

src/localization.js

Lines changed: 55 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -29,146 +29,136 @@ export class Localization {
2929
return new Localization(locale, bundle);
3030
}
3131

32+
/**
33+
* @private
34+
* @param {string} messageId
35+
* @param {Record<string, FluentVariable>} [args]
36+
* @returns {string}
37+
*/
38+
_formatMessage(messageId, args) {
39+
const message = this.bundle.getMessage(messageId);
40+
if (!message?.value) {
41+
return `Localization error: message '${messageId}' not found.`;
42+
}
43+
return this.bundle.formatPattern(message.value, args);
44+
}
45+
3246
/** @type (expectedTypes: string | string[], actualType: string) => string */
3347
getTypeErrorMessage(expectedTypes, actualType) {
34-
const message =/** @type Message */ (this.bundle.getMessage("type-error"));
35-
36-
if (typeof expectedTypes === "string") {
37-
expectedTypes = [expectedTypes];
38-
}
48+
const types = Array.isArray(expectedTypes) ? expectedTypes : [expectedTypes];
49+
const expected = new Intl.ListFormat(this.locale, { type: "disjunction" }).format(
50+
types.map((type) => JSON.stringify(type))
51+
);
3952

40-
const expected = expectedTypes.map((type) => JSON.stringify(type));
41-
return this.bundle.formatPattern(/** @type Pattern */(message.value), {
42-
expected: new Intl.ListFormat(this.locale, { type: "disjunction" }).format(expected),
53+
return this._formatMessage("type-error", {
54+
expected,
4355
actual: JSON.stringify(actualType)
4456
});
4557
}
4658

4759
/** @type (limit: number) => string */
4860
getMinLengthErrorMessage(limit) {
49-
const message =/** @type Message */ (this.bundle.getMessage("min-length-error"));
50-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit });
61+
return this._formatMessage("min-length-error", { limit });
5162
}
5263

5364
/** @type (limit: number) => string */
5465
getMaxLengthErrorMessage(limit) {
55-
const message =/** @type Message */ (this.bundle.getMessage("max-length-error"));
56-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit });
66+
return this._formatMessage("max-length-error", { limit });
5767
}
5868

5969
/** @type (limit: number) => string */
6070
getMaximumErrorMessage(limit) {
61-
const message =/** @type Message */ (this.bundle.getMessage("maximum-error"));
62-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit });
71+
return this._formatMessage("maximum-error", { limit });
6372
}
6473

6574
/** @type (limit: number) => string */
6675
getMinimumErrorMessage(limit) {
67-
const message =/** @type Message */ (this.bundle.getMessage("minimum-error"));
68-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit });
76+
return this._formatMessage("minimum-error", { limit });
6977
}
7078

7179
/** @type (limit: number) => string */
7280
getExclusiveMinimumErrorMessage(limit) {
73-
const message =/** @type Message */ (this.bundle.getMessage("exclusive-minimum-error"));
74-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit });
81+
return this._formatMessage("exclusive-minimum-error", { limit });
7582
}
7683

7784
/** @type (limit: number) => string */
7885
getExclusiveMaximumErrorMessage(limit) {
79-
const message =/** @type Message */ (this.bundle.getMessage("exclusive-maximum-error"));
80-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit });
86+
return this._formatMessage("exclusive-maximum-error", { limit });
8187
}
8288

83-
/** @type (instanceLocation: string, missingProperties: string | string[]) => string */
89+
/** @type (instanceLocation: string, missingProperties: string[]) => string */
8490
getRequiredErrorMessage(instanceLocation, missingProperties) {
85-
const requiredList = new Intl.ListFormat(this.locale, { type: "conjunction" }).format(missingProperties);
86-
const message =/** @type Message */ (this.bundle.getMessage("required-error"));
87-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), {
91+
return this._formatMessage("required-error", {
8892
instanceLocation,
89-
missingProperties: requiredList
93+
missingProperties: new Intl.ListFormat(this.locale, { type: "conjunction" }).format(missingProperties)
9094
});
9195
}
9296

9397
/** @type (divisor: number) => string */
9498
getMultipleOfErrorMessage(divisor) {
95-
const message =/** @type Message */ (this.bundle.getMessage("multiple-of-error"));
96-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { divisor });
99+
return this._formatMessage("multiple-of-error", { divisor });
97100
}
98101

99102
/** @type (limit: number) => string */
100103
getMaxPropertiesErrorMessage(limit) {
101-
const message =/** @type Message */ (this.bundle.getMessage("max-properties-error"));
102-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit });
104+
return this._formatMessage("max-properties-error", { limit });
103105
}
104106

105107
/** @type (limit: number) => string */
106108
getMinPropertiesErrorMessage(limit) {
107-
const message =/** @type Message */ (this.bundle.getMessage("min-properties-error"));
108-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit });
109+
return this._formatMessage("min-properties-error", { limit });
109110
}
110111

111112
/** @type (expectedValue: FluentVariable) => string */
112113
getConstErrorMessage(expectedValue) {
113-
const message =/** @type Message */ (this.bundle.getMessage("const-error")); // type doubt here
114-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { expectedValue });
114+
return this._formatMessage("const-error", { expectedValue });
115115
}
116116

117117
/** @type (limit: number) => string */
118118
getMaxItemsErrorMessage(limit) {
119-
const message =/** @type Message */ (this.bundle.getMessage("max-items-error"));
120-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit });
119+
return this._formatMessage("max-items-error", { limit });
121120
}
122121

123122
/** @type (limit: number) => string */
124123
getMinItemsErrorMessage(limit) {
125-
const message =/** @type Message */ (this.bundle.getMessage("min-items-error"));
126-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit });
124+
return this._formatMessage("min-items-error", { limit });
127125
}
128126

129127
/** @type () => string */
130128
getUniqueItemsErrorMessage() {
131-
const message =/** @type Message */ (this.bundle.getMessage("unique-items-error"));
132-
return this.bundle.formatPattern(/** @type Pattern */ (message.value));
129+
return this._formatMessage("unique-items-error");
133130
}
134131

135132
/** @type (format: string) => string */
136133
getFormatErrorMessage(format) {
137-
const message =/** @type Message */ (this.bundle.getMessage("format-error"));
138-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { format });
134+
return this._formatMessage("format-error", { format });
139135
}
140136

141137
/** @type (pattern: string) => string */
142138
getPatternErrorMessage(pattern) {
143-
const message =/** @type Message */ (this.bundle.getMessage("pattern-error"));
144-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { pattern });
139+
return this._formatMessage("pattern-error", { pattern });
145140
}
146141

147142
/** @type () => string */
148143
getContainsErrorMessage() {
149-
const message =/** @type Message */ (this.bundle.getMessage("contains-error"));
150-
return this.bundle.formatPattern(/** @type Pattern */ (message.value));
144+
return this._formatMessage("contains-error");
151145
}
152146

153147
/** @type () => string */
154148
getNotErrorMessage() {
155-
const message =/** @type Message */ (this.bundle.getMessage("not-error"));
156-
return this.bundle.formatPattern(/** @type Pattern */ (message.value));
149+
return this._formatMessage("not-error");
157150
}
158151

159152
/** @type (propertyName: string) => string */
160153
getAdditionalPropertiesErrorMessage(propertyName) {
161-
const message =/** @type Message */ (this.bundle.getMessage("additional-properties-error"));
162-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), { propertyName });
154+
return this._formatMessage("additional-properties-error", { propertyName });
163155
}
164156

165-
/** @type (property: string, missingDependents: string | string[]) => string */
157+
/** @type (property: string, missingDependents: string[]) => string */
166158
getDependentRequiredErrorMessage(property, missingDependents) {
167-
const dependentsList = new Intl.ListFormat(this.locale, { type: "conjunction" }).format(missingDependents);
168-
const message =/** @type Message */ (this.bundle.getMessage("dependent-required-error"));
169-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), {
159+
return this._formatMessage("dependent-required-error", {
170160
property,
171-
missingDependents: dependentsList
161+
missingDependents: new Intl.ListFormat(this.locale, { type: "conjunction" }).format(missingDependents)
172162
});
173163
}
174164

@@ -191,21 +181,20 @@ export class Localization {
191181
* @returns {string}
192182
*/
193183
getEnumErrorMessage(args) {
194-
const message = /** @type Message */ (this.bundle.getMessage("enum-error"));
184+
const formattedArgs = {
185+
variant: args.variant,
186+
instanceValue: `"${args.instanceValue}"`,
187+
suggestion: "",
188+
allowedValues: ""
189+
};
190+
195191
if (args.variant === "fallback") {
196192
const quotedValues = args.allowedValues.map((value) => JSON.stringify(value));
197-
const formattedList = new Intl.ListFormat(this.locale, { type: "disjunction" }).format(quotedValues);
198-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), {
199-
variant: "fallback",
200-
instanceValue: `"${args.instanceValue}"`,
201-
allowedValues: formattedList
202-
});
193+
formattedArgs.allowedValues = new Intl.ListFormat(this.locale, { type: "disjunction" }).format(quotedValues);
203194
} else {
204-
return this.bundle.formatPattern(/** @type Pattern */ (message.value), {
205-
variant: "suggestion",
206-
instanceValue: `"${args.instanceValue}"`,
207-
suggestion: args.suggestion
208-
});
195+
formattedArgs.suggestion = args.suggestion;
209196
}
197+
198+
return this._formatMessage("enum-error", formattedArgs);
210199
}
211200
}

src/normalizeOutputFormat/normalizeOutput.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ keywordHandlers["https://json-schema.org/keyword/propertyNames"] = {
310310
return outputs;
311311
}
312312
for (const propertyName of Instance.keys(instance)) {
313+
propertyName.pointer = propertyName.pointer.replace(/^\*/, "");
313314
outputs.push(evaluateSchema(propertyNamesSchemaLocation, propertyName, context));
314315
}
315316
return outputs;
@@ -657,7 +658,7 @@ export const constructErrorIndex = async (outputUnit, schema, errorIndex = {}) =
657658
}
658659
const absoluteKeywordLocation = errorOutputUnit.absoluteKeywordLocation
659660
?? await toAbsoluteKeywordLocation(schema, /** @type string */ (errorOutputUnit.keywordLocation));
660-
const instanceLocation = /** @type string */ (normalizeInstanceLocation(errorOutputUnit.instanceLocation));
661+
const instanceLocation = normalizeInstanceLocation(/** @type string */ (errorOutputUnit.instanceLocation));
661662
errorIndex[absoluteKeywordLocation] ??= {};
662663
errorIndex[absoluteKeywordLocation][instanceLocation] = true;
663664
await constructErrorIndex(errorOutputUnit, schema, errorIndex);
@@ -677,9 +678,10 @@ export async function toAbsoluteKeywordLocation(schema, keywordLocation) {
677678
return `${schema.document.baseUri}#${schema.cursor}`;
678679
}
679680

680-
/** @type {(location: string | undefined) => string | undefined} */
681+
/** @type {(location: string) => string} */
681682
function normalizeInstanceLocation(location) {
682-
return location?.startsWith("/") || location === "" ? `#${location}` : location;
683+
const instanceLocation = location.startsWith("/") || location === "" ? `#${location}` : location;
684+
return instanceLocation.replace(/(#|^)\*\//, "$1/");
683685
}
684686

685687
/**

0 commit comments

Comments
 (0)