Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import importPlugin from "eslint-plugin-import";
export default tseslint.config(
...tseslint.configs.recommendedTypeChecked,
...tseslint.configs.stylisticTypeChecked,
importPlugin.flatConfigs.recommended, // eslint-disable-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
importPlugin.flatConfigs.typescript, // eslint-disable-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
importPlugin.flatConfigs.recommended,
importPlugin.flatConfigs.typescript,
stylistic.configs.customize({
arrowParens: true,
braceStyle: "1tbs",
Expand Down Expand Up @@ -36,6 +36,7 @@ export default tseslint.config(
}],
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
"@typescript-eslint/consistent-indexed-object-style": "off",
"no-console": "error",

// Imports
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@fluent/bundle": "^0.19.1",
"@hyperjump/browser": "^1.3.1",
"@hyperjump/json-schema": "^1.16.0",
"@hyperjump/pact": "^1.4.0",
"leven": "^4.0.0"
}
}
27 changes: 27 additions & 0 deletions src/error-handlers/additionalProperties.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as Instance from "@hyperjump/json-schema/instance/experimental";

/**
* @import { ErrorHandler, ErrorObject } from "../index.d.ts"
*/

/** @type ErrorHandler */
// eslint-disable-next-line @typescript-eslint/require-await
const additionalProperties = async (normalizedErrors, instance, localization) => {
/** @type ErrorObject[] */
const errors = [];
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 = /** @type string */ (Instance.uri(instance).split("/").pop());
errors.push({
message: localization.getAdditionalPropertiesErrorMessage(notAllowedValue),
instanceLocation: Instance.uri(instance),
schemaLocation: schemaLocation
});
}
}
}
return errors;
};

export default additionalProperties;
64 changes: 64 additions & 0 deletions src/error-handlers/anyOf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { getSchema } from "@hyperjump/json-schema/experimental";
import * as Schema from "@hyperjump/browser";
import * as Instance from "@hyperjump/json-schema/instance/experimental";
import { getErrors } from "../error-handling.js";

/**
* @import { ErrorHandler, ErrorObject, NormalizedOutput } from "../index.d.ts"
*/

/** @type ErrorHandler */
const anyOf = async (normalizedErrors, instance, localization) => {
/** @type ErrorObject[] */
const errors = [];

if (normalizedErrors["https://json-schema.org/keyword/anyOf"]) {
for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/anyOf"]) {
/** @type NormalizedOutput[] */
const alternatives = [];
const allAlternatives = /** @type NormalizedOutput[] */ (normalizedErrors["https://json-schema.org/keyword/anyOf"][schemaLocation]);
for (const alternative of allAlternatives) {
if (Object.values(alternative[Instance.uri(instance)]["https://json-schema.org/keyword/type"]).every((valid) => valid)) {
alternatives.push(alternative);
}
}
// case 1 where no. alternative matched the type of the instance.
if (alternatives.length === 0) {
/** @type Set<string> */
const expectedTypes = new Set();
for (const alternative of allAlternatives) {
for (const instanceLocation in alternative) {
if (instanceLocation === Instance.uri(instance)) {
for (const schemaLocation in alternative[instanceLocation]["https://json-schema.org/keyword/type"]) {
const keyword = await getSchema(schemaLocation);
const expectedType = /** @type string */ (Schema.value(keyword));
expectedTypes.add(expectedType);
}
}
}
}
errors.push({
message: localization.getTypeErrorMessage([...expectedTypes], Instance.typeOf(instance)),
instanceLocation: Instance.uri(instance),
schemaLocation: schemaLocation
});
} else if (alternatives.length === 1) { // case 2 when only one type match
return getErrors(alternatives[0], instance, localization);
} else if (instance.type === "object") {
let targetAlternativeIndex = -1;
for (const alternative of alternatives) {
targetAlternativeIndex++;
for (const instanceLocation in alternative) {
if (instanceLocation !== "#") {
return getErrors(alternatives[targetAlternativeIndex], instance, localization);
}
}
}
}
}
}

return errors;
};

export default anyOf;
30 changes: 30 additions & 0 deletions src/error-handlers/const.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getSchema } from "@hyperjump/json-schema/experimental";
import * as Schema from "@hyperjump/browser";
import * as Instance from "@hyperjump/json-schema/instance/experimental";

/**
* @import { ErrorHandler, ErrorObject } from "../index.d.ts"
*/

/** @type ErrorHandler */
const const_ = async (normalizedErrors, instance, localization) => {
/** @type ErrorObject[] */
const errors = [];

if (normalizedErrors["https://json-schema.org/keyword/const"]) {
for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/const"]) {
if (!normalizedErrors["https://json-schema.org/keyword/const"][schemaLocation]) {
const keyword = await getSchema(schemaLocation);
errors.push({
message: localization.getConstErrorMessage(Schema.value(keyword)),
instanceLocation: Instance.uri(instance),
schemaLocation: schemaLocation
});
}
}
}

return errors;
};

export default const_;
30 changes: 30 additions & 0 deletions src/error-handlers/contains.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as Instance from "@hyperjump/json-schema/instance/experimental";
import { getErrors } from "../error-handling.js";

/**
* @import { ErrorHandler, ErrorObject, NormalizedOutput } from "../index.d.ts"
*/

/** @type ErrorHandler */
const contains = async (normalizedErrors, instance, localization) => {
/** @type ErrorObject[] */
const errors = [];
if (normalizedErrors["https://json-schema.org/keyword/contains"]) {
for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/contains"]) {
errors.push({
message: localization.getContainsErrorMessage(),
instanceLocation: Instance.uri(instance),
schemaLocation: schemaLocation
});
const containsNodes = /** @type NormalizedOutput[] */(normalizedErrors["https://json-schema.org/keyword/contains"][schemaLocation]);
for (const errorOutput of containsNodes) {
const containsSubErrors = await getErrors(errorOutput, instance, localization);
errors.push(...containsSubErrors);
}
}
}

return errors;
};

export default contains;
40 changes: 40 additions & 0 deletions src/error-handlers/dependentRequired.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { getSchema } from "@hyperjump/json-schema/experimental";
import * as Schema from "@hyperjump/browser";
import * as Instance from "@hyperjump/json-schema/instance/experimental";

/**
* @import { ErrorHandler, ErrorObject } from "../index.d.ts"
*/

/** @type ErrorHandler */
const dependentRequired = async (normalizedErrors, instance, localization) => {
/** @type ErrorObject[] */
const errors = [];

if (normalizedErrors["https://json-schema.org/keyword/dependentRequired"]) {
for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/dependentRequired"]) {
if (!normalizedErrors["https://json-schema.org/keyword/dependentRequired"][schemaLocation]) {
const keyword = await getSchema(schemaLocation);
const dependentRequired = /** @type {Record<string, string[]>} */(Schema.value(keyword));
for (const propertyName in dependentRequired) {
if (Instance.has(propertyName, instance)) {
const required = dependentRequired[propertyName];
const missing = required.filter((prop) => !Instance.has(prop, instance));

if (missing.length > 0) {
errors.push({
message: localization.getDependentRequiredErrorMessage(propertyName, [...missing]),
instanceLocation: Instance.uri(instance),
schemaLocation: schemaLocation
});
}
}
}
}
}
}

return errors;
};

export default dependentRequired;
59 changes: 59 additions & 0 deletions src/error-handlers/enum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { getSchema } from "@hyperjump/json-schema/experimental";
import * as Schema from "@hyperjump/browser";
import * as Instance from "@hyperjump/json-schema/instance/experimental";
import leven from "leven";

/**
* @import { ErrorHandler, ErrorObject } from "../index.d.ts"
*/

/** @type ErrorHandler */
const enum_ = async (normalizedErrors, instance, localization) => {
/** @type ErrorObject[] */
const errors = [];

if (normalizedErrors["https://json-schema.org/keyword/enum"]) {
for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/enum"]) {
if (!normalizedErrors["https://json-schema.org/keyword/enum"][schemaLocation]) {
const keyword = await getSchema(schemaLocation);

/** @type {Array<string>} */
const allowedValues = Schema.value(keyword);
const currentValue = /** @type {string} */ (Instance.value(instance));

const bestMatch = allowedValues
.map((value) => ({
value,
weight: leven(value, currentValue)
}))
.sort((a, b) => a.weight - b.weight)[0];
let message;
if (
allowedValues.length === 1
|| (bestMatch && bestMatch.weight < bestMatch.value.length)
) {
message = localization.getEnumErrorMessage({
variant: "suggestion",
instanceValue: currentValue,
suggestion: bestMatch.value
});
} else {
message = localization.getEnumErrorMessage({
variant: "fallback",
instanceValue: currentValue,
allowedValues: allowedValues
});
}
errors.push({
message,
instanceLocation: Instance.uri(instance),
schemaLocation: schemaLocation
});
}
}
}

return errors;
};

export default enum_;
30 changes: 30 additions & 0 deletions src/error-handlers/exclusiveMaximum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getSchema } from "@hyperjump/json-schema/experimental";
import * as Schema from "@hyperjump/browser";
import * as Instance from "@hyperjump/json-schema/instance/experimental";

/**
* @import { ErrorHandler, ErrorObject } from "../index.d.ts"
*/

/** @type ErrorHandler */
const exclusiveMaximum = async (normalizedErrors, instance, localization) => {
/** @type ErrorObject[] */
const errors = [];

if (normalizedErrors["https://json-schema.org/keyword/exclusiveMaximum"]) {
for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/exclusiveMaximum"]) {
if (!normalizedErrors["https://json-schema.org/keyword/exclusiveMaximum"][schemaLocation]) {
const keyword = await getSchema(schemaLocation);
errors.push({
message: localization.getExclusiveMaximumErrorMessage(Schema.value(keyword)),
instanceLocation: Instance.uri(instance),
schemaLocation: schemaLocation
});
}
}
}

return errors;
};

export default exclusiveMaximum;
30 changes: 30 additions & 0 deletions src/error-handlers/exclusiveMinimum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getSchema } from "@hyperjump/json-schema/experimental";
import * as Schema from "@hyperjump/browser";
import * as Instance from "@hyperjump/json-schema/instance/experimental";

/**
* @import { ErrorHandler, ErrorObject } from "../index.d.ts"
*/

/** @type ErrorHandler */
const exclusiveMinimum = async (normalizedErrors, instance, localization) => {
/** @type ErrorObject[] */
const errors = [];

if (normalizedErrors["https://json-schema.org/keyword/exclusiveMinimum"]) {
for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/exclusiveMinimum"]) {
if (!normalizedErrors["https://json-schema.org/keyword/exclusiveMinimum"][schemaLocation]) {
const keyword = await getSchema(schemaLocation);
errors.push({
message: localization.getExclusiveMinimumErrorMessage(Schema.value(keyword)),
instanceLocation: Instance.uri(instance),
schemaLocation: schemaLocation
});
}
}
}

return errors;
};

export default exclusiveMinimum;
30 changes: 30 additions & 0 deletions src/error-handlers/format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getSchema } from "@hyperjump/json-schema/experimental";
import * as Schema from "@hyperjump/browser";
import * as Instance from "@hyperjump/json-schema/instance/experimental";

/**
* @import { ErrorHandler, ErrorObject } from "../index.d.ts"
*/

/** @type ErrorHandler */
const format = async (normalizedErrors, instance, localization) => {
/** @type ErrorObject[] */
const errors = [];

if (normalizedErrors["https://json-schema.org/keyword/format"]) {
for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/format"]) {
if (!normalizedErrors["https://json-schema.org/keyword/format"][schemaLocation]) {
const keyword = await getSchema(schemaLocation);
errors.push({
message: localization.getFormatErrorMessage(Schema.value(keyword)),
instanceLocation: Instance.uri(instance),
schemaLocation: schemaLocation
});
}
}
}

return errors;
};

export default format;
Loading