diff --git a/src/error-handlers/anyOf.js b/src/error-handlers/anyOf.js index fd20ce0..748a594 100644 --- a/src/error-handlers/anyOf.js +++ b/src/error-handlers/anyOf.js @@ -7,7 +7,7 @@ import { getErrors } from "../json-schema-errors.js"; */ /** @type ErrorHandler */ -const anyOfErrorHandler = async (normalizedErrors, instance, localization) => { +const anyOfErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; @@ -51,13 +51,13 @@ const anyOfErrorHandler = async (normalizedErrors, instance, localization) => { } // The alternative passed all the filters - alternatives.push(await getErrors(alternative, instance, localization)); + alternatives.push(getErrors(alternative, instance, localization, ast)); } // If all alternatives were filtered out, default to returning all of them if (alternatives.length === 0) { for (const alternative of anyOf) { - alternatives.push(await getErrors(alternative, instance, localization)); + alternatives.push(getErrors(alternative, instance, localization, ast)); } } diff --git a/src/error-handlers/boolean-schema.js b/src/error-handlers/boolean-schema.js index 80b8d49..6ca00ba 100644 --- a/src/error-handlers/boolean-schema.js +++ b/src/error-handlers/boolean-schema.js @@ -5,7 +5,7 @@ import * as Instance from "@hyperjump/json-schema/instance/experimental"; */ /** @type ErrorHandler */ -const booleanSchemaErrorHandler = async (normalizedErrors, instance, localization) => { +const booleanSchemaErrorHandler = (normalizedErrors, instance, localization) => { /** @type ErrorObject[] */ const errors = []; diff --git a/src/error-handlers/contains.js b/src/error-handlers/contains.js index 400e09c..c5c45e8 100644 --- a/src/error-handlers/contains.js +++ b/src/error-handlers/contains.js @@ -1,13 +1,13 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue, getSiblingKeywordLocation } from "../json-schema-errors.js"; /** + * @import { ContainsAst } from "../normalization-handlers/contains.js" * @import { ContainsRange, ErrorHandler, ErrorObject } from "../index.d.ts" */ /** @type ErrorHandler */ -const containsErrorHandler = async (normalizedErrors, instance, localization) => { +const containsErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; @@ -25,25 +25,21 @@ const containsErrorHandler = async (normalizedErrors, instance, localization) => /** @type string[] */ const schemaLocations = [schemaLocation]; + const contains = /** @type ContainsAst */ (getCompiledKeywordValue(ast, schemaLocation)); + /** @type ContainsRange */ const range = {}; - const parentLocation = pointerPop(schemaLocation); - - for (const minContainsLocation in normalizedErrors["https://json-schema.org/keyword/minContains"]) { - if (pointerPop(minContainsLocation) === parentLocation) { - const minContainsNode = await getSchema(minContainsLocation); - range.minContains = /** @type number */ (Schema.value(minContainsNode)); + if (typeof contains !== "string") { + if (contains.minContains !== 1) { + range.minContains = contains.minContains; + const minContainsLocation = getSiblingKeywordLocation(ast, schemaLocation, "https://json-schema.org/keyword/minContains"); schemaLocations.push(minContainsLocation); - break; } - } - for (const maxContainsLocation in normalizedErrors["https://json-schema.org/keyword/maxContains"]) { - if (pointerPop(maxContainsLocation) === parentLocation) { - const maxContainsNode = await getSchema(maxContainsLocation); - range.maxContains = /** @type number */ (Schema.value(maxContainsNode)); + if (contains.maxContains !== Number.MAX_SAFE_INTEGER) { + range.maxContains = contains.maxContains; + const maxContainsLocation = getSiblingKeywordLocation(ast, schemaLocation, "https://json-schema.org/keyword/maxContains"); schemaLocations.push(maxContainsLocation); - break; } } @@ -58,7 +54,4 @@ const containsErrorHandler = async (normalizedErrors, instance, localization) => return errors; }; -/** @type (pointer: string) => string */ -const pointerPop = (pointer) => pointer.replace(/\/[^/]+$/, ""); - export default containsErrorHandler; diff --git a/src/error-handlers/draft-04/dependencies.js b/src/error-handlers/draft-04/dependencies.js index c01a9f5..b492516 100644 --- a/src/error-handlers/draft-04/dependencies.js +++ b/src/error-handlers/draft-04/dependencies.js @@ -5,7 +5,7 @@ import { getErrors } from "../../json-schema-errors.js"; */ /** @type ErrorHandler */ -const dependenciesErrorHandler = async (normalizedErrors, instance, localization) => { +const dependenciesErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; @@ -16,7 +16,7 @@ const dependenciesErrorHandler = async (normalizedErrors, instance, localization const dependentSchemaOutputs = normalizedErrors["https://json-schema.org/keyword/draft-04/dependencies"][schemaLocation]; for (const dependentSchemaOutput of dependentSchemaOutputs) { - const dependentSchemaErrors = await getErrors(dependentSchemaOutput, instance, localization); + const dependentSchemaErrors = getErrors(dependentSchemaOutput, instance, localization, ast); errors.push(...dependentSchemaErrors); } } diff --git a/src/error-handlers/format.js b/src/error-handlers/format.js index 760c473..6176544 100644 --- a/src/error-handlers/format.js +++ b/src/error-handlers/format.js @@ -1,13 +1,12 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue } from "../json-schema-errors.js"; /** * @import { ErrorHandler, ErrorObject } from "../index.d.ts" */ /** @type ErrorHandler */ -const formatErrorHandler = async (normalizedErrors, instance, localization) => { +const formatErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; @@ -26,8 +25,7 @@ const formatErrorHandler = async (normalizedErrors, instance, localization) => { continue; } - const keyword = await getSchema(schemaLocation); - const format = /** @type string */ (Schema.value(keyword)); + const format = /** @type string */ (getCompiledKeywordValue(ast, schemaLocation)); errors.push({ message: localization.getFormatErrorMessage(format), diff --git a/src/error-handlers/maxItems.js b/src/error-handlers/maxItems.js index a821a34..712a312 100644 --- a/src/error-handlers/maxItems.js +++ b/src/error-handlers/maxItems.js @@ -1,13 +1,12 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue } from "../json-schema-errors.js"; /** * @import { ErrorHandler, ErrorObject } from "../index.d.ts" */ /** @type ErrorHandler */ -const maxItemsErrorHandler = async (normalizedErrors, instance, localization) => { +const maxItemsErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; let lowestMaxItems = Infinity; @@ -18,8 +17,7 @@ const maxItemsErrorHandler = async (normalizedErrors, instance, localization) => continue; } - const keyword = await getSchema(schemaLocation); - const maxItems = /** @type number */ (Schema.value(keyword)); + const maxItems = /** @type number */ (getCompiledKeywordValue(ast, schemaLocation)); if (maxItems < lowestMaxItems) { lowestMaxItems = maxItems; diff --git a/src/error-handlers/maxLength.js b/src/error-handlers/maxLength.js index 030ba3d..9d6ad8f 100644 --- a/src/error-handlers/maxLength.js +++ b/src/error-handlers/maxLength.js @@ -1,13 +1,12 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue } from "../json-schema-errors.js"; /** * @import { ErrorHandler, ErrorObject } from "../index.d.ts" */ /** @type ErrorHandler */ -const maxLengthErrorHandler = async (normalizedErrors, instance, localization) => { +const maxLengthErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; let lowestMaxLength = Infinity; @@ -18,8 +17,7 @@ const maxLengthErrorHandler = async (normalizedErrors, instance, localization) = continue; } - const keyword = await getSchema(schemaLocation); - const maxLength = /** @type number */ (Schema.value(keyword)); + const maxLength = /** @type number */ (getCompiledKeywordValue(ast, schemaLocation)); if (maxLength < lowestMaxLength) { lowestMaxLength = maxLength; diff --git a/src/error-handlers/maxProperties.js b/src/error-handlers/maxProperties.js index 0551717..d32ff28 100644 --- a/src/error-handlers/maxProperties.js +++ b/src/error-handlers/maxProperties.js @@ -1,13 +1,12 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue } from "../json-schema-errors.js"; /** * @import { ErrorHandler, ErrorObject } from "../index.d.ts" */ /** @type ErrorHandler */ -const maxPropertiesErrorHandler = async (normalizedErrors, instance, localization) => { +const maxPropertiesErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; let lowestMaxProperties = Infinity; @@ -17,8 +16,7 @@ const maxPropertiesErrorHandler = async (normalizedErrors, instance, localizatio continue; } - const keyword = await getSchema(schemaLocation); - const maxProperties = /** @type number */ (Schema.value(keyword)); + const maxProperties = /** @type number */ (getCompiledKeywordValue(ast, schemaLocation)); if (maxProperties < lowestMaxProperties) { lowestMaxProperties = maxProperties; diff --git a/src/error-handlers/maximum.js b/src/error-handlers/maximum.js index ee8b62d..cddb18e 100644 --- a/src/error-handlers/maximum.js +++ b/src/error-handlers/maximum.js @@ -1,13 +1,12 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue, getSiblingKeywordLocation } from "../json-schema-errors.js"; /** * @import { ErrorHandler } from "../index.d.ts" */ /** @type ErrorHandler */ -const maximumErrorHandler = async (normalizedErrors, instance, localization) => { +const maximumErrorHandler = (normalizedErrors, instance, localization, ast) => { let lowestMaximum = Infinity; let isExclusive = false; @@ -19,8 +18,7 @@ const maximumErrorHandler = async (normalizedErrors, instance, localization) => continue; } - const keyword = await getSchema(schemaLocation); - const maximum = /** @type number */ (Schema.value(keyword)); + const maximum = /** @type number */ (getCompiledKeywordValue(ast, schemaLocation)); if (maximum < lowestMaximum) { lowestMaximum = maximum; schemaLocations = [schemaLocation]; @@ -32,8 +30,7 @@ const maximumErrorHandler = async (normalizedErrors, instance, localization) => continue; } - const keyword = await getSchema(schemaLocation); - const exclusiveMaximum = /** @type number */ (Schema.value(keyword)); + const exclusiveMaximum = /** @type number */ (getCompiledKeywordValue(ast, schemaLocation)); if (exclusiveMaximum < lowestMaximum) { lowestMaximum = exclusiveMaximum; isExclusive = true; @@ -46,27 +43,15 @@ const maximumErrorHandler = async (normalizedErrors, instance, localization) => continue; } - const parentLocation = pointerPop(schemaLocation); - /** @type string */ - let exclusiveLocation = ""; - for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/draft-04/exclusiveMaximum"]) { - const exclusiveParentLocation = pointerPop(schemaLocation); - if (exclusiveParentLocation === parentLocation) { - const exclusiveNode = await getSchema(schemaLocation); - if (Schema.value(exclusiveNode)) { - exclusiveLocation = schemaLocation; - } - break; - } - } - - const keywordNode = await getSchema(schemaLocation); - const maximum = /** @type number */ (Schema.value(keywordNode)); - + const [maximum, exclusive] = /** @type [number, boolean] */ (getCompiledKeywordValue(ast, schemaLocation)); if (maximum < lowestMaximum) { lowestMaximum = maximum; - isExclusive = !!exclusiveLocation; - schemaLocations = exclusiveLocation ? [schemaLocation, exclusiveLocation] : [schemaLocation]; + isExclusive = exclusive; + schemaLocations = [schemaLocation]; + if (exclusive) { + const exclusiveLocation = getSiblingKeywordLocation(ast, schemaLocation, "https://json-schema.org/keyword/draft-04/exclusiveMaximum"); + schemaLocations.push(exclusiveLocation); + } } } @@ -87,7 +72,4 @@ const maximumErrorHandler = async (normalizedErrors, instance, localization) => } }; -/** @type (pointer: string) => string */ -const pointerPop = (pointer) => pointer.replace(/\/[^/]+$/, ""); - export default maximumErrorHandler; diff --git a/src/error-handlers/minItems.js b/src/error-handlers/minItems.js index 13da179..cad6c15 100644 --- a/src/error-handlers/minItems.js +++ b/src/error-handlers/minItems.js @@ -1,13 +1,12 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue } from "../json-schema-errors.js"; /** * @import { ErrorHandler, ErrorObject } from "../index.d.ts" */ /** @type ErrorHandler */ -const minItemsErrorHandler = async (normalizedErrors, instance, localization) => { +const minItemsErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; let highestMinItem = 0; @@ -18,8 +17,7 @@ const minItemsErrorHandler = async (normalizedErrors, instance, localization) => continue; } - const keyword = await getSchema(schemaLocation); - const minItems = /** @type number */ (Schema.value(keyword)); + const minItems = /** @type number */ (getCompiledKeywordValue(ast, schemaLocation)); if (minItems > highestMinItem) { highestMinItem = minItems; diff --git a/src/error-handlers/minLength.js b/src/error-handlers/minLength.js index b740f80..ec1d473 100644 --- a/src/error-handlers/minLength.js +++ b/src/error-handlers/minLength.js @@ -1,13 +1,12 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue } from "../json-schema-errors.js"; /** * @import { ErrorHandler, ErrorObject } from "../index.d.ts" */ /** @type ErrorHandler */ -const minLengthErrorHandler = async (normalizedErrors, instance, localization) => { +const minLengthErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; let highestMinLength = -Infinity; @@ -18,8 +17,7 @@ const minLengthErrorHandler = async (normalizedErrors, instance, localization) = continue; } - const keyword = await getSchema(schemaLocation); - const minLength = /** @type number */ (Schema.value(keyword)); + const minLength = /** @type number */ (getCompiledKeywordValue(ast, schemaLocation)); if (minLength > highestMinLength) { highestMinLength = minLength; diff --git a/src/error-handlers/minProperties.js b/src/error-handlers/minProperties.js index e736244..52fa0a3 100644 --- a/src/error-handlers/minProperties.js +++ b/src/error-handlers/minProperties.js @@ -1,13 +1,12 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue } from "../json-schema-errors.js"; /** * @import { ErrorHandler, ErrorObject } from "../index.d.ts" */ /** @type ErrorHandler */ -const minPropertiesErrorHandler = async (normalizedErrors, instance, localization) => { +const minPropertiesErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; @@ -19,8 +18,7 @@ const minPropertiesErrorHandler = async (normalizedErrors, instance, localizatio continue; } - const keyword = await getSchema(schemaLocation); - const minProperties = /** @type number */ (Schema.value(keyword)); + const minProperties = /** @type number */ (getCompiledKeywordValue(ast, schemaLocation)); if (minProperties > highestMinProperties) { highestMinProperties = minProperties; diff --git a/src/error-handlers/minimum.js b/src/error-handlers/minimum.js index 467836c..7dd7a80 100644 --- a/src/error-handlers/minimum.js +++ b/src/error-handlers/minimum.js @@ -1,13 +1,12 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue, getSiblingKeywordLocation } from "../json-schema-errors.js"; /** * @import { ErrorHandler } from "../index.d.ts" */ /** @type ErrorHandler */ -const minimumErrorHandler = async (normalizedErrors, instance, localization) => { +const minimumErrorHandler = (normalizedErrors, instance, localization, ast) => { let highestMinimum = -Infinity; let isExclusive = false; /** @type string[] */ @@ -18,8 +17,7 @@ const minimumErrorHandler = async (normalizedErrors, instance, localization) => continue; } - const keyword = await getSchema(schemaLocation); - const minimum = /** @type number */ (Schema.value(keyword)); + const minimum = /** @type number */ (getCompiledKeywordValue(ast, schemaLocation)); if (minimum > highestMinimum) { highestMinimum = minimum; @@ -33,8 +31,7 @@ const minimumErrorHandler = async (normalizedErrors, instance, localization) => continue; } - const keyword = await getSchema(schemaLocation); - const exclusiveMinimum = /** @type number */ (Schema.value(keyword)); + const exclusiveMinimum = /** @type number */ (getCompiledKeywordValue(ast, schemaLocation)); if (exclusiveMinimum > highestMinimum) { highestMinimum = exclusiveMinimum; @@ -42,31 +39,21 @@ const minimumErrorHandler = async (normalizedErrors, instance, localization) => schemaLocations = [schemaLocation]; } } + for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/draft-04/minimum"]) { if (normalizedErrors["https://json-schema.org/keyword/draft-04/minimum"][schemaLocation]) { continue; } - const parentLocation = pointerPop(schemaLocation); - - let exclusiveLocation = ""; - for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/draft-04/exclusiveMinimum"]) { - const exclusiveParentLocation = pointerPop(schemaLocation); - if (exclusiveParentLocation === parentLocation) { - const exclusiveNode = await getSchema(schemaLocation); - if (Schema.value(exclusiveNode)) { - exclusiveLocation = schemaLocation; - } - break; - } - } - - const keywordNode = await getSchema(schemaLocation); - const minimum = /** @type number */ (Schema.value(keywordNode)); + const [minimum, exclusive] = /** @type [number, boolean] */ (getCompiledKeywordValue(ast, schemaLocation)); if (minimum > highestMinimum) { highestMinimum = minimum; - isExclusive = !!exclusiveLocation; - schemaLocations = exclusiveLocation ? [schemaLocation, exclusiveLocation] : [schemaLocation]; + isExclusive = exclusive; + schemaLocations = [schemaLocation]; + if (exclusive) { + const exclusiveLocation = getSiblingKeywordLocation(ast, schemaLocation, "https://json-schema.org/keyword/draft-04/exclusiveMinimum"); + schemaLocations.push(exclusiveLocation); + } } } @@ -87,7 +74,4 @@ const minimumErrorHandler = async (normalizedErrors, instance, localization) => } }; -/** @type (pointer: string) => string */ -const pointerPop = (pointer) => pointer.replace(/\/[^/]+$/, ""); - export default minimumErrorHandler; diff --git a/src/error-handlers/multipleOf.js b/src/error-handlers/multipleOf.js index 3cba32b..6229988 100644 --- a/src/error-handlers/multipleOf.js +++ b/src/error-handlers/multipleOf.js @@ -1,13 +1,12 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue } from "../json-schema-errors.js"; /** * @import { ErrorHandler, ErrorObject } from "../index.d.ts" */ /** @type ErrorHandler */ -const multipleOfErrorHandler = async (normalizedErrors, instance, localization) => { +const multipleOfErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; @@ -22,8 +21,7 @@ const multipleOfErrorHandler = async (normalizedErrors, instance, localization) hasError = true; } - const keyword = await getSchema(schemaLocation); - const multipleOf = /** @type number */ (Schema.value(keyword)); + const multipleOf = /** @type number */ (getCompiledKeywordValue(ast, schemaLocation)); combinedMultipleOf = combinedMultipleOf === null ? multipleOf : lcm(combinedMultipleOf, multipleOf); schemaLocations.push(schemaLocation); diff --git a/src/error-handlers/not.js b/src/error-handlers/not.js index 94d1a5e..2d4afc9 100644 --- a/src/error-handlers/not.js +++ b/src/error-handlers/not.js @@ -5,7 +5,7 @@ import * as Instance from "@hyperjump/json-schema/instance/experimental"; */ /** @type ErrorHandler */ -const notErrorHandler = async (normalizedErrors, instance, localization) => { +const notErrorHandler = (normalizedErrors, instance, localization) => { /** @type ErrorObject[] */ const errors = []; diff --git a/src/error-handlers/oneOf.js b/src/error-handlers/oneOf.js index 63aa4ef..f8fe643 100644 --- a/src/error-handlers/oneOf.js +++ b/src/error-handlers/oneOf.js @@ -7,7 +7,7 @@ import { getErrors } from "../json-schema-errors.js"; */ /** @type ErrorHandler */ -const oneOfErrorHandler = async (normalizedErrors, instance, localization) => { +const oneOfErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; @@ -51,7 +51,7 @@ const oneOfErrorHandler = async (normalizedErrors, instance, localization) => { } // The alternative passed all the filters - const alternativeErrors = await getErrors(alternative, instance, localization); + const alternativeErrors = getErrors(alternative, instance, localization, ast); if (alternativeErrors.length) { alternatives.push(alternativeErrors); } else { @@ -61,7 +61,7 @@ const oneOfErrorHandler = async (normalizedErrors, instance, localization) => { if (matchCount === 0 && alternatives.length === 0) { for (const alternative of oneOf) { - const alternativeErrors = await getErrors(alternative, instance, localization); + const alternativeErrors = getErrors(alternative, instance, localization, ast); alternatives.push(alternativeErrors); } } diff --git a/src/error-handlers/pattern.js b/src/error-handlers/pattern.js index a25150a..719aec2 100644 --- a/src/error-handlers/pattern.js +++ b/src/error-handlers/pattern.js @@ -1,13 +1,12 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue } from "../json-schema-errors.js"; /** * @import { ErrorHandler, ErrorObject } from "../index.d.ts" */ /** @type ErrorHandler */ -const patternErrorHandler = async (normalizedErrors, instance, localization) => { +const patternErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type ErrorObject[] */ const errors = []; @@ -16,8 +15,8 @@ const patternErrorHandler = async (normalizedErrors, instance, localization) => continue; } - const keyword = await getSchema(schemaLocation); - const pattern = /** @type string */ (Schema.value(keyword)); + const compiledPattern = /** @type RegExp */ (getCompiledKeywordValue(ast, schemaLocation)); + const pattern = compiledPattern.source; errors.push({ message: localization.getPatternErrorMessage(pattern), diff --git a/src/error-handlers/required.js b/src/error-handlers/required.js index c9174ae..63ed92f 100644 --- a/src/error-handlers/required.js +++ b/src/error-handlers/required.js @@ -1,6 +1,5 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; +import { getCompiledKeywordValue } from "../json-schema-errors.js"; /** * @import { ErrorHandler } from "../index.d.ts" @@ -8,7 +7,7 @@ import * as Instance from "@hyperjump/json-schema/instance/experimental"; */ /** @type ErrorHandler */ -const requiredErrorHandler = async (normalizedErrors, instance, localization) => { +const requiredErrorHandler = (normalizedErrors, instance, localization, ast) => { /** @type {Set} */ const allMissingRequired = new Set(); const allSchemaLocations = []; @@ -19,8 +18,7 @@ const requiredErrorHandler = async (normalizedErrors, instance, localization) => } allSchemaLocations.push(schemaLocation); - const keyword = await getSchema(schemaLocation); - const required = /** @type string[] */ (Schema.value(keyword)); + const required = /** @type string[] */ (getCompiledKeywordValue(ast, schemaLocation)); addMissingProperties(required, instance, allMissingRequired); } @@ -31,14 +29,12 @@ const requiredErrorHandler = async (normalizedErrors, instance, localization) => } allSchemaLocations.push(schemaLocation); - const keyword = await getSchema(schemaLocation); + const dependencies = /** @type {[string, string[]][]} */ (getCompiledKeywordValue(ast, schemaLocation)); - for await (const [propertyName, dependencyNode] of Schema.entries(keyword)) { + for (const [propertyName, requiredProperties] of dependencies) { if (!Instance.has(propertyName, instance)) { continue; } - - const requiredProperties = /** @type string[] */ (Schema.value(dependencyNode)); addMissingProperties(requiredProperties, instance, allMissingRequired); } } @@ -48,16 +44,16 @@ const requiredErrorHandler = async (normalizedErrors, instance, localization) => continue; } - const keyword = await getSchema(schemaLocation); + const dependencies = /** @type {[string, unknown][]} */ (getCompiledKeywordValue(ast, schemaLocation)); let hasArrayFormDependencies = false; - for await (const [propertyName, dependency] of Schema.entries(keyword)) { - if (!Instance.has(propertyName, instance) || Schema.typeOf(dependency) !== "array") { + for (const [propertyName, dependency] of dependencies) { + if (!Instance.has(propertyName, instance) || !Array.isArray(dependency)) { continue; } hasArrayFormDependencies = true; - const dependencyArray = /** @type {string[]} */ (Schema.value(dependency)); + const dependencyArray = /** @type {string[]} */ (dependency); addMissingProperties(dependencyArray, instance, allMissingRequired); } diff --git a/src/error-handlers/typeConstEnum.js b/src/error-handlers/typeConstEnum.js index 17c2841..f074cda 100644 --- a/src/error-handlers/typeConstEnum.js +++ b/src/error-handlers/typeConstEnum.js @@ -1,7 +1,5 @@ -import { getSchema } from "@hyperjump/json-schema/experimental"; -import * as Schema from "@hyperjump/browser"; import * as Instance from "@hyperjump/json-schema/instance/experimental"; -import jsonStringify from "json-stringify-deterministic"; +import { getCompiledKeywordValue } from "../json-schema-errors.js"; /** * @import { ErrorHandler, Json } from "../index.d.ts" @@ -10,7 +8,7 @@ import jsonStringify from "json-stringify-deterministic"; const ALL_TYPES = new Set(["null", "boolean", "number", "string", "array", "object", "integer"]); /** @type {ErrorHandler} */ -const typeConstEnumErrorHandler = async (normalizedErrors, instance, localization) => { +const typeConstEnumErrorHandler = (normalizedErrors, instance, localization, ast) => { let allowedTypes = new Set(ALL_TYPES); /** @type {string[]} */ const failedTypeLocations = []; @@ -19,9 +17,8 @@ const typeConstEnumErrorHandler = async (normalizedErrors, instance, localizatio if (!normalizedErrors["https://json-schema.org/keyword/type"][schemaLocation]) { failedTypeLocations.push(schemaLocation); - const keyword = await getSchema(schemaLocation); /** @type {string | string[]} */ - const value = Schema.value(keyword); + const value = /** @type {string | string[]} */ (getCompiledKeywordValue(ast, schemaLocation)); const types = Array.isArray(value) ? value : [value]; /** @type {Set} */ const keywordTypes = new Set(types); @@ -52,10 +49,10 @@ const typeConstEnumErrorHandler = async (normalizedErrors, instance, localizatio failedConstLocations.push(schemaLocation); } - const keyword = await getSchema(schemaLocation); const keywordJson = new Set(); - if (allowedTypes.has(Schema.typeOf(keyword))) { - keywordJson.add(jsonStringify(Schema.value(keyword))); + const constValueJson = /** @type string */ (getCompiledKeywordValue(ast, schemaLocation)); + if (allowedTypes.has(jsonTypeOf(constValueJson))) { + keywordJson.add(constValueJson); } else { typeFiltered = true; } @@ -69,11 +66,11 @@ const typeConstEnumErrorHandler = async (normalizedErrors, instance, localizatio failedEnumLocations.push(schemaLocation); } - const keyword = await getSchema(schemaLocation); const keywordJson = new Set(); - for await (const enumValueNode of Schema.iter(keyword)) { - if (allowedTypes.has(Schema.typeOf(enumValueNode))) { - keywordJson.add(jsonStringify(Schema.value(enumValueNode))); + const enumValuesJson = /** @type string[] */ (getCompiledKeywordValue(ast, schemaLocation)); + for (const enumValueJson of enumValuesJson) { + if (allowedTypes.has(jsonTypeOf(enumValueJson))) { + keywordJson.add(enumValueJson); } else { typeFiltered = true; } @@ -114,4 +111,25 @@ const typeConstEnumErrorHandler = async (normalizedErrors, instance, localizatio } }; +/** + * @param {string} value + * @returns {string} + */ +const jsonTypeOf = (value) => { + const firstChar = value[0]; + if (firstChar === "n") { + return "null"; + } else if (firstChar === "[") { + return "array"; + } else if ((firstChar >= "0" && firstChar <= "9") || firstChar === "-") { + return "number"; + } else if (firstChar === "{") { + return "object"; + } else if (firstChar === "\"") { + return "string"; + } else { + return "boolean"; + } +}; + export default typeConstEnumErrorHandler; diff --git a/src/error-handlers/uniqueItems.js b/src/error-handlers/uniqueItems.js index 2a70836..25c07bb 100644 --- a/src/error-handlers/uniqueItems.js +++ b/src/error-handlers/uniqueItems.js @@ -6,7 +6,7 @@ import jsonStringify from "json-stringify-deterministic"; */ /** @type ErrorHandler */ -const uniqueItemsErrorHandler = async (normalizedErrors, instance, localization) => { +const uniqueItemsErrorHandler = (normalizedErrors, instance, localization) => { /** @type ErrorObject[] */ const errors = []; diff --git a/src/error-handlers/unknown.js b/src/error-handlers/unknown.js index e84f744..c8540cf 100644 --- a/src/error-handlers/unknown.js +++ b/src/error-handlers/unknown.js @@ -6,7 +6,7 @@ import { pointerSegments } from "@hyperjump/json-pointer"; */ /** @type ErrorHandler */ -const unknownErrorHandler = async (normalizedErrors, instance, localization) => { +const unknownErrorHandler = (normalizedErrors, instance, localization) => { /** @type ErrorObject[] */ const errors = []; diff --git a/src/index.d.ts b/src/index.d.ts index 32729dd..9f3e106 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -143,13 +143,13 @@ export const addErrorHandler: (handler: ErrorHandler) => void; * A function that transforms normalized errors for one or more keywords into human * readable messages. */ -export type ErrorHandler = (normalizedErrors: InstanceOutput, instance: JsonNode, localization: Localization) => Promise; +export type ErrorHandler = (normalizedErrors: InstanceOutput, instance: JsonNode, localization: Localization, ast: AST) => ErrorObject[]; /** * Converts the normalized error format to human readable errors. It's used to * build errors in applicator error handlers. */ -export const getErrors: (normalizedErrors: NormalizedOutput, instance: JsonNode, localization: Localization) => Promise; +export const getErrors: (normalizedErrors: NormalizedOutput, instance: JsonNode, localization: Localization, ast: AST) => ErrorObject[]; export type { Localization }; diff --git a/src/json-schema-errors.js b/src/json-schema-errors.js index a19f5b5..da0bcd2 100644 --- a/src/json-schema-errors.js +++ b/src/json-schema-errors.js @@ -9,18 +9,23 @@ import { JsonSchemaErrorsOutputPlugin } from "./output-plugin.js"; /** * @import * as API from "./index.d.ts" * @import { Browser } from "@hyperjump/browser"; - * @import { SchemaDocument, CompiledSchema } from "@hyperjump/json-schema/experimental"; + * @import { AST, SchemaDocument, CompiledSchema, Node } from "@hyperjump/json-schema/experimental"; * @import { JsonNode } from "@hyperjump/json-schema/instance/experimental" */ /** @type API.jsonSchemaErrors */ export const jsonSchemaErrors = async (errorOutput, schemaUri, instance, options = {}) => { const rootInstance = Instance.fromJs(instance); - const [normalizedErrors, localization] = await Promise.all([ - normalizedOutput(rootInstance, errorOutput, schemaUri), - Localization.forLocale(options.locale ?? "en-US") - ]); - return await getErrors(normalizedErrors, rootInstance, localization); + const schema = await getSchema(schemaUri); + const errorIndex = await constructErrorIndex(errorOutput, schema); + const { schemaUri: compiledSchemaUri, ast } = await compile(schema); + const normalizedErrors = evaluateSchema(compiledSchemaUri, rootInstance, { + ast, + errorIndex, + plugins: [...ast.plugins] + }); + const localization = await Localization.forLocale(options.locale ?? "en-US"); + return getErrors(normalizedErrors, rootInstance, localization, ast); }; /** @type Record */ @@ -31,14 +36,6 @@ export const setNormalizationHandler = (schemaUri, handler) => { normalizationHandlers[schemaUri] = handler; }; -/** @type (value: JsonNode, errorOutput: API.OutputUnit, subjectUri: string) => Promise */ -async function normalizedOutput(value, errorOutput, subjectUri) { - const schema = await getSchema(subjectUri); - const errorIndex = await constructErrorIndex(errorOutput, schema); - const { schemaUri, ast } = await compile(schema); - return evaluateSchema(schemaUri, value, { ast, errorIndex, plugins: [...ast.plugins] }); -} - /** @type (outputUnit: API.OutputUnit, schema: Browser, errorIndex?: API.ErrorIndex) => Promise */ const constructErrorIndex = async (outputUnit, schema, errorIndex = {}) => { if (outputUnit.valid) { @@ -71,11 +68,7 @@ const constructErrorIndex = async (outputUnit, schema, errorIndex = {}) => { return errorIndex; }; -/** - * @param {Browser} schema - * @param {string} keywordLocation - * @returns {Promise} - */ +/** @type (schema: Browser, keywordLocation: string) => Promise */ async function toAbsoluteKeywordLocation(schema, keywordLocation) { if (keywordLocation.startsWith("#")) { keywordLocation = keywordLocation.slice(1); @@ -181,14 +174,14 @@ export const addErrorHandler = (errorHandler) => { }; /** @type API.getErrors */ -export const getErrors = async (normalizedErrors, rootInstance, localization) => { +export const getErrors = (normalizedErrors, rootInstance, localization, ast) => { /** @type API.ErrorObject[] */ const errors = []; for (const instanceLocation in normalizedErrors) { const instance = /** @type JsonNode */ (Instance.get(instanceLocation, rootInstance)); for (const errorHandler of errorHandlers) { - const errorObject = await errorHandler(normalizedErrors[instanceLocation], instance, localization); + const errorObject = errorHandler(normalizedErrors[instanceLocation], instance, localization, ast); errors.push(...errorObject); } } @@ -196,6 +189,42 @@ export const getErrors = async (normalizedErrors, rootInstance, localization) => return errors; }; +/** @type (ast: AST, schemaLocation: string) => Node[] | boolean | undefined */ +const getParentNode = (ast, schemaLocation) => { + const parentLocation = schemaLocation.replace(/\/[^/]+$/, ""); + return ast[parentLocation]; +}; + +/** @type (ast: AST, schemaLocation: string) => unknown */ +export const getCompiledKeywordValue = (ast, schemaLocation) => { + const parentNode = getParentNode(ast, schemaLocation); + if (typeof parentNode === "boolean") { + return parentNode; + } + + const node = parentNode?.find(([, keywordLocation]) => keywordLocation === schemaLocation); + if (!node) { + throw Error("AST node not found"); + } + + return node[2]; +}; + +/** @type (ast: AST, schemaLocation: string, siblingKeywordUri: string) => string */ +export const getSiblingKeywordLocation = (ast, schemaLocation, siblingKeywordUri) => { + let parentNode = getParentNode(ast, schemaLocation); + if (typeof parentNode === "boolean") { + parentNode = undefined; + } + + const node = parentNode?.find(([keywordUri]) => keywordUri === siblingKeywordUri); + if (!node) { + throw Error("AST node not found"); + } + + return node[1]; +}; + /** * @overload * @param {string} schemaUri @@ -241,7 +270,7 @@ const evaluateCompiledSchema = async (compiledSchema, instance, options = {}) => } else { return { valid, - errors: await getErrors(outputPlugin.output, jsonNode, localization) + errors: getErrors(outputPlugin.output, jsonNode, localization, compiledSchema.ast) }; } };