diff --git a/src/index.js b/src/index.js index 0b9fa51..a2f74d6 100644 --- a/src/index.js +++ b/src/index.js @@ -111,7 +111,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/minLength"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should be at least ${Schema.value(keyword)} characters`, + message: localization.getMinLengthErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -131,7 +131,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/maxLength"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should be atmost ${Schema.value(keyword)} characters long.`, + message: localization.getMaxLengthErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -171,7 +171,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/maximum"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should be less than or equal to ${Schema.value(keyword)}.`, + message: localization.getMaximumErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -191,7 +191,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/minimum"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should be greater than or equal to ${Schema.value(keyword)}.`, + message: localization.getMinimumErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -211,7 +211,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/exclusiveMinimum"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should be greater than ${Schema.value(keyword)}.`, + message: localization.getExclusiveMinimumErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -231,7 +231,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/exclusiveMaximum"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should be less than ${Schema.value(keyword)}.`, + message: localization.getExclusiveMaximumErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -256,7 +256,7 @@ const errorHandlers = [ required.delete(propertyName); } errors.push({ - message: `"${Instance.uri(instance)}" is missing required property(s): ${[...required].join(", ")}.`, + message: localization.getRequiredErrorMessage(Instance.uri(instance), [...required]), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -276,7 +276,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/multipleOf"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should be of multiple of ${Schema.value(keyword)}.`, + message: localization.getMultipleOfErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -296,7 +296,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/maxProperties"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should have maximum ${Schema.value(keyword)} properties.`, + message: localization.getMaxPropertiesErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -316,7 +316,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/minProperties"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should have minimum ${Schema.value(keyword)} properties.`, + message: localization.getMinPropertiesErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -336,7 +336,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/const"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should be equal to ${Schema.value(keyword)}.`, + message: localization.getConstErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -366,23 +366,25 @@ const errorHandlers = [ weight: leven(value, currentValue) })) .sort((a, b) => a.weight - b.weight)[0]; - - let suggestion = ""; + let message; if ( allowedValues.length === 1 || (bestMatch && bestMatch.weight < bestMatch.value.length) ) { - suggestion = ` Did you mean "${bestMatch.value}"?`; - errors.push({ - message: `Unexpected value "${currentValue}". ${suggestion}`, - instanceLocation: Instance.uri(instance), - schemaLocation: schemaLocation + message = localization.getEnumErrorMessage({ + variant: "suggestion", + instanceValue: currentValue, + suggestion: bestMatch.value + }); + } else { + message = localization.getEnumErrorMessage({ + variant: "fallback", + instanceValue: currentValue, + allowedValues: allowedValues }); - continue; } - errors.push({ - message: `Unexpected value "${currentValue}". Expected one of: ${allowedValues.join(",")}.`, + message, instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -402,7 +404,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/maxItems"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should contain maximum ${Schema.value(keyword)} items in the array.`, + message: localization.getMaxItemsErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -422,7 +424,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/minItems"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should contain minimum ${Schema.value(keyword)} items in the array.`, + message: localization.getMinItemsErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -442,7 +444,7 @@ const errorHandlers = [ for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/uniqueItems"]) { if (!normalizedErrors["https://json-schema.org/keyword/uniqueItems"][schemaLocation]) { errors.push({ - message: `The instance should have unique items in the array.`, + message: localization.getUniqueItemsErrorMessage(), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -462,7 +464,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/format"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should match the format: ${Schema.value(keyword)}.`, + message: localization.getFormatErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -482,7 +484,7 @@ const errorHandlers = [ if (!normalizedErrors["https://json-schema.org/keyword/pattern"][schemaLocation]) { const keyword = await getSchema(schemaLocation); errors.push({ - message: `The instance should match the pattern: ${Schema.value(keyword)}.`, + message: localization.getPatternErrorMessage(Schema.value(keyword)), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -499,7 +501,7 @@ const errorHandlers = [ if (normalizedErrors["https://json-schema.org/keyword/contains"]) { for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/contains"]) { errors.push({ - message: `A required value is missing from the list`, + message: localization.getContainsErrorMessage(), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -522,7 +524,7 @@ const errorHandlers = [ if (normalizedErrors["https://json-schema.org/keyword/not"]) { for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/not"]) { errors.push({ - message: `The instance is not allowed to be used in this schema.`, + message: localization.getNotErrorMessage(), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -539,9 +541,9 @@ const errorHandlers = [ if (normalizedErrors["https://json-schema.org/validation"]) { for (const schemaLocation in normalizedErrors["https://json-schema.org/validation"]) { if (!normalizedErrors["https://json-schema.org/validation"][schemaLocation] && schemaLocation.endsWith("/additionalProperties")) { - const notAllowedValue = Instance.uri(instance).split("/").pop(); + const notAllowedValue = /** @type string */(Instance.uri(instance).split("/").pop()); errors.push({ - message: `The property "${notAllowedValue}" is not allowed.`, + message: localization.getAdditionalPropertiesErrorMessage(notAllowedValue), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); @@ -567,7 +569,7 @@ const errorHandlers = [ if (missing.length > 0) { errors.push({ - message: `Property "${propertyName}" requires property(s): ${missing.join(", ")}.`, + message: localization.getDependentRequiredErrorMessage(propertyName, [...missing]), instanceLocation: Instance.uri(instance), schemaLocation: schemaLocation }); diff --git a/src/keywordErrorMessage.test.js b/src/keywordErrorMessage.test.js index 066c564..7d2e280 100644 --- a/src/keywordErrorMessage.test.js +++ b/src/keywordErrorMessage.test.js @@ -39,7 +39,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/minLength", instanceLocation: "#", - message: "The instance should be at least 3 characters" + message: localization.getMinLengthErrorMessage(3) } ]); }); @@ -67,7 +67,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/maxLength", instanceLocation: "#", - message: "The instance should be atmost 3 characters long." + message: localization.getMaxLengthErrorMessage(3) } ]); }); @@ -123,7 +123,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/maximum", instanceLocation: "#", - message: `The instance should be less than or equal to 10.` + message: localization.getMaximumErrorMessage(10) } ]); }); @@ -151,7 +151,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/minimum", instanceLocation: "#", - message: `The instance should be greater than or equal to 10.` + message: localization.getMinimumErrorMessage(10) } ]); }); @@ -179,7 +179,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/exclusiveMaximum", instanceLocation: "#", - message: `The instance should be less than 10.` + message: localization.getExclusiveMaximumErrorMessage(10) } ]); }); @@ -207,7 +207,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/exclusiveMinimum", instanceLocation: "#", - message: `The instance should be greater than 10.` + message: localization.getExclusiveMinimumErrorMessage(10) } ]); }); @@ -236,7 +236,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/required", instanceLocation: "#", - message: `"#" is missing required property(s): baz.` + message: localization.getRequiredErrorMessage("#", ["baz"]) } ]); }); @@ -265,7 +265,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/multipleOf", instanceLocation: "#", - message: `The instance should be of multiple of 5.` + message: localization.getMultipleOfErrorMessage(5) } ]); }); @@ -294,7 +294,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/maxProperties", instanceLocation: "#", - message: `The instance should have maximum 2 properties.` + message: localization.getMaxPropertiesErrorMessage(2) } ]); }); @@ -323,7 +323,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/minProperties", instanceLocation: "#", - message: `The instance should have minimum 2 properties.` + message: localization.getMinPropertiesErrorMessage(2) } ]); }); @@ -352,7 +352,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/const", instanceLocation: "#", - message: `The instance should be equal to 2.` + message: localization.getConstErrorMessage(2) } ]); }); @@ -381,7 +381,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/enum", instanceLocation: "#", - message: `Unexpected value "rwd". Did you mean "red"?` + message: localization.getEnumErrorMessage({ variant: "suggestion", instanceValue: "rwd", suggestion: "red" }) }]); }); @@ -408,7 +408,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/maxItems", instanceLocation: "#", - message: `The instance should contain maximum 3 items in the array.` + message: localization.getMaxItemsErrorMessage(3) } ]); }); @@ -436,7 +436,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/minItems", instanceLocation: "#", - message: `The instance should contain minimum 3 items in the array.` + message: localization.getMinItemsErrorMessage(3) } ]); }); @@ -464,7 +464,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/uniqueItems", instanceLocation: "#", - message: `The instance should have unique items in the array.` + message: localization.getUniqueItemsErrorMessage() } ]); }); @@ -491,7 +491,7 @@ describe("Error messages", async () => { { schemaLocation: "https://example.com/main#/format", instanceLocation: "#", - message: "The instance should match the format: email." + message: localization.getFormatErrorMessage("email") } ]); }); @@ -518,7 +518,7 @@ describe("Error messages", async () => { { schemaLocation: "https://example.com/main#/pattern", instanceLocation: "#", - message: "The instance should match the pattern: ^[a-z]+$." + message: localization.getPatternErrorMessage("^[a-z]+$") } ]); }); @@ -678,7 +678,7 @@ describe("Error messages", async () => { { schemaLocation: `https://example.com/main#/anyOf/0/minLength`, instanceLocation: "#", - message: "The instance should be at least 5 characters" + message: localization.getMinLengthErrorMessage(5) } ]); }); @@ -739,7 +739,7 @@ describe("Error messages", async () => { { schemaLocation: "https://example.com/main#/anyOf/1/properties/ID/pattern", instanceLocation: "#/ID", - message: "The instance should match the pattern: ^[0-9\\-]+$." + message: localization.getPatternErrorMessage("^[0-9\\-]+$") } ]); }); @@ -882,7 +882,7 @@ describe("Error messages", async () => { { schemaLocation: "https://example.com/main#/$defs/numberSchema/minimum", instanceLocation: "#/foo", - message: "The instance should be greater than or equal to 10." + message: localization.getMinimumErrorMessage(10) } ]); }); @@ -929,7 +929,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([ { instanceLocation: "#", - message: "A required value is missing from the list", + message: localization.getContainsErrorMessage(), schemaLocation: "https://example.com/main#/contains" }, { @@ -939,12 +939,12 @@ describe("Error messages", async () => { }, { instanceLocation: "#/1", - message: "The instance should be of multiple of 2.", + message: localization.getMultipleOfErrorMessage(2), schemaLocation: "https://example.com/main#/contains/multipleOf" }, { instanceLocation: "#/2", - message: "The instance should be of multiple of 2.", + message: localization.getMultipleOfErrorMessage(2), schemaLocation: "https://example.com/main#/contains/multipleOf" } ]); @@ -972,7 +972,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([ { instanceLocation: "#", - message: `The instance should be greater than or equal to 0.`, + message: localization.getMinimumErrorMessage(0), schemaLocation: "https://example.com/main#/then/minimum" } ]); @@ -1000,7 +1000,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([ { instanceLocation: "#", - message: `The instance should be greater than or equal to 0.`, + message: localization.getMinimumErrorMessage(0), schemaLocation: "https://example.com/main#/else/minimum" } ]); @@ -1085,7 +1085,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([ { instanceLocation: "#*/Foo", - message: "The instance should match the pattern: ^[a-z]*$.", + message: localization.getPatternErrorMessage("^[a-z]*$"), schemaLocation: "https://example.com/main#/propertyNames/pattern" } ]); @@ -1130,12 +1130,12 @@ describe("Error messages", async () => { expect(result.errors).to.eql([ { instanceLocation: "#/unknown_property", - message: `The property "unknown_property" is not allowed.`, + message: localization.getAdditionalPropertiesErrorMessage("unknown_property"), schemaLocation: "https://example.com/main#/additionalProperties" }, { instanceLocation: "#/unknown_property1", - message: `The property "unknown_property1" is not allowed.`, + message: localization.getAdditionalPropertiesErrorMessage("unknown_property1"), schemaLocation: "https://example.com/main#/additionalProperties" } ]); @@ -1167,7 +1167,7 @@ describe("Error messages", async () => { expect(result.errors).to.eql([ { instanceLocation: "#", - message: `Property "foo" requires property(s): baz.`, + message: localization.getDependentRequiredErrorMessage("foo", ["baz"]), schemaLocation: "https://example.com/main#/dependentRequired" } ]); diff --git a/src/localization.js b/src/localization.js index 5e41f89..dde1d1f 100644 --- a/src/localization.js +++ b/src/localization.js @@ -2,7 +2,8 @@ import { readFile } from "node:fs/promises"; import { FluentBundle, FluentResource } from "@fluent/bundle"; /** - * @import { Message, Pattern } from "@fluent/bundle/esm/ast.d.ts" + * @import { Pattern} from "@fluent/bundle/esm/ast.d.ts" + * @import { FluentVariable, Message } from "@fluent/bundle" */ export class Localization { /** @@ -42,4 +43,169 @@ export class Localization { actual: JSON.stringify(actualType) }); } + + /** @type (limit: number) => string */ + getMinLengthErrorMessage(limit) { + const message =/** @type Message */ (this.bundle.getMessage("min-length-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit }); + } + + /** @type (limit: number) => string */ + getMaxLengthErrorMessage(limit) { + const message =/** @type Message */ (this.bundle.getMessage("max-length-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit }); + } + + /** @type (limit: number) => string */ + getMaximumErrorMessage(limit) { + const message =/** @type Message */ (this.bundle.getMessage("maximum-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit }); + } + + /** @type (limit: number) => string */ + getMinimumErrorMessage(limit) { + const message =/** @type Message */ (this.bundle.getMessage("minimum-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit }); + } + + /** @type (limit: number) => string */ + getExclusiveMinimumErrorMessage(limit) { + const message =/** @type Message */ (this.bundle.getMessage("exclusive-minimum-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit }); + } + + /** @type (limit: number) => string */ + getExclusiveMaximumErrorMessage(limit) { + const message =/** @type Message */ (this.bundle.getMessage("exclusive-maximum-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit }); + } + + /** @type (instanceLocation: string, missingProperties: string | string[]) => string */ + getRequiredErrorMessage(instanceLocation, missingProperties) { + const requiredList = new Intl.ListFormat(this.locale, { type: "conjunction" }).format(missingProperties); + const message =/** @type Message */ (this.bundle.getMessage("required-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { + instanceLocation, + missingProperties: requiredList + }); + } + + /** @type (divisor: number) => string */ + getMultipleOfErrorMessage(divisor) { + const message =/** @type Message */ (this.bundle.getMessage("multiple-of-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { divisor }); + } + + /** @type (limit: number) => string */ + getMaxPropertiesErrorMessage(limit) { + const message =/** @type Message */ (this.bundle.getMessage("max-properties-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit }); + } + + /** @type (limit: number) => string */ + getMinPropertiesErrorMessage(limit) { + const message =/** @type Message */ (this.bundle.getMessage("min-properties-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit }); + } + + /** @type (expectedValue: FluentVariable) => string */ + getConstErrorMessage(expectedValue) { + const message =/** @type Message */ (this.bundle.getMessage("const-error")); // type doubt here + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { expectedValue }); + } + + /** @type (limit: number) => string */ + getMaxItemsErrorMessage(limit) { + const message =/** @type Message */ (this.bundle.getMessage("max-items-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit }); + } + + /** @type (limit: number) => string */ + getMinItemsErrorMessage(limit) { + const message =/** @type Message */ (this.bundle.getMessage("min-items-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { limit }); + } + + /** @type () => string */ + getUniqueItemsErrorMessage() { + const message =/** @type Message */ (this.bundle.getMessage("unique-items-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value)); + } + + /** @type (format: string) => string */ + getFormatErrorMessage(format) { + const message =/** @type Message */ (this.bundle.getMessage("format-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { format }); + } + + /** @type (pattern: string) => string */ + getPatternErrorMessage(pattern) { + const message =/** @type Message */ (this.bundle.getMessage("pattern-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { pattern }); + } + + /** @type () => string */ + getContainsErrorMessage() { + const message =/** @type Message */ (this.bundle.getMessage("contains-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value)); + } + + /** @type () => string */ + getNotErrorMessage() { + const message =/** @type Message */ (this.bundle.getMessage("not-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value)); + } + + /** @type (propertyName: string) => string */ + getAdditionalPropertiesErrorMessage(propertyName) { + const message =/** @type Message */ (this.bundle.getMessage("additional-properties-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { propertyName }); + } + + /** @type (property: string, missingDependents: string | string[]) => string */ + getDependentRequiredErrorMessage(property, missingDependents) { + const dependentsList = new Intl.ListFormat(this.locale, { type: "conjunction" }).format(missingDependents); + const message =/** @type Message */ (this.bundle.getMessage("dependent-required-error")); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { + property, + missingDependents: dependentsList + }); + } + + /** + * @typedef {Object} EnumSuggestionArgs + * @property {"suggestion"} variant + * @property {string} instanceValue + * @property {string} suggestion + */ + + /** + * @typedef {Object} EnumFallbackArgs + * @property {"fallback"} variant + * @property {string} instanceValue + * @property {string[]} allowedValues + */ + + /** + * @param {EnumSuggestionArgs | EnumFallbackArgs} args + * @returns {string} + */ + getEnumErrorMessage(args) { + const message = /** @type Message */ (this.bundle.getMessage("enum-error")); + if (args.variant === "fallback") { + const quotedValues = args.allowedValues.map((value) => JSON.stringify(value)); + const formattedList = new Intl.ListFormat(this.locale, { type: "disjunction" }).format(quotedValues); + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { + variant: "fallback", + instanceValue: `"${args.instanceValue}"`, + allowedValues: formattedList + }); + } else { + return this.bundle.formatPattern(/** @type Pattern */ (message.value), { + variant: "suggestion", + instanceValue: `"${args.instanceValue}"`, + suggestion: args.suggestion + }); + } + } } diff --git a/src/normalizeOutputFormat/normalizeOutput.test.js b/src/normalizeOutputFormat/normalizeOutput.test.js index c8c1cb4..36648e0 100644 --- a/src/normalizeOutputFormat/normalizeOutput.test.js +++ b/src/normalizeOutputFormat/normalizeOutput.test.js @@ -2,11 +2,13 @@ import { afterEach, describe, expect, test } from "vitest"; import { normalizedErrorOuput } from "./normalizeOutput.js"; import { betterJsonSchemaErrors } from "../index.js"; import { registerSchema, unregisterSchema } from "@hyperjump/json-schema/draft-2020-12"; +import { Localization } from "../localization.js"; /** * @import { OutputFormat} from "../index.d.ts" */ -describe("Error Output Normalization", () => { +describe("Error Output Normalization", async () => { + const localization = await Localization.forLocale("en-US"); const schemaUri = "https://example.com/main"; const schemaUri1 = "https://example.com/polygon"; @@ -38,7 +40,7 @@ describe("Error Output Normalization", () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/minLength", instanceLocation: "#", - message: "The instance should be at least 3 characters" + message: localization.getMinLengthErrorMessage(3) } ]); }); @@ -66,7 +68,7 @@ describe("Error Output Normalization", () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/minLength", instanceLocation: "#", - message: "The instance should be at least 3 characters" + message: localization.getMinLengthErrorMessage(3) }]); }); @@ -93,7 +95,7 @@ describe("Error Output Normalization", () => { expect(result.errors).to.eql([{ schemaLocation: "https://example.com/main#/minLength", instanceLocation: "#", - message: "The instance should be at least 3 characters" + message: localization.getMinLengthErrorMessage(3) }]); }); @@ -367,7 +369,7 @@ describe("Error Output Normalization", () => { { schemaLocation: "https://example.com/main#/$defs/lengthDefinition/minLength", instanceLocation: "#/foo", - message: "The instance should be at least 3 characters" + message: localization.getMinLengthErrorMessage(3) } ]); }); diff --git a/src/translations/en-US.ftl b/src/translations/en-US.ftl index e25acf3..7fbdcac 100644 --- a/src/translations/en-US.ftl +++ b/src/translations/en-US.ftl @@ -1 +1,25 @@ -type-error = The instance should be of type {$expected} but found {$actual}. \ No newline at end of file +type-error = The instance should be of type {$expected} but found {$actual}. +min-length-error = The instance should be atleast {$limit} characters. +max-length-error = The instance should be atmost {$limit} characters long. +maximum-error = The instance should be less than or equal to {$limit}. +minimum-error = The instance should be greater than or equal to {$limit}. +exclusive-maximum-error = The instance should be less than {$limit}. +exclusive-minimum-error = The instance should be greater than {$limit}. +required-error = "{$instanceLocation}" is missing required property(s): {$missingProperties}. +multiple-of-error = The instance should be a multiple of {$divisor}. +max-properties-error = The instance should have a maximum of {$limit} properties. +min-properties-error = The instance should have a minimum of {$limit} properties. +const-error = The instance should be equal to {$expectedValue}. +max-items-error = The instance should contain a maximum of {$limit} items in the array. +min-items-error = The instance should contain a minimum of {$limit} items in the array. +unique-items-error = The instance should have unique items in the array. +format-error = The instance should match the format: {$format}. +pattern-error = The instance should match the pattern: {$pattern}. +contains-error = A required value is missing from the list. +not-error = The instance is not allowed to be used in this schema. +additional-properties-error = The property "{$propertyName}" is not allowed. +dependent-required-error = Property "{$property}" requires property(s): {$missingDependents}. +enum-error = { $variant -> + [suggestion] Unexpected value {$instanceValue}. Did you mean {$suggestion}? + *[fallback] Unexpected value {$instanceValue}. Expected one of: {$allowedValues}. +} \ No newline at end of file