diff --git a/src/error-handlers/type.js b/src/error-handlers/type.js index 39a9dc6..99bcb9b 100644 --- a/src/error-handlers/type.js +++ b/src/error-handlers/type.js @@ -6,26 +6,53 @@ import * as Instance from "@hyperjump/json-schema/instance/experimental"; * @import { ErrorHandler, ErrorObject } from "../index.d.ts" */ +const ALL_TYPES = new Set(["null", "boolean", "number", "string", "array", "object", "integer"]); + /** @type ErrorHandler */ const typeErrorHandler = async (normalizedErrors, instance, localization) => { /** @type ErrorObject[] */ const errors = []; - for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/type"]) { - if (normalizedErrors["https://json-schema.org/keyword/type"][schemaLocation]) { - continue; + if (normalizedErrors["https://json-schema.org/keyword/type"]) { + /** @type {Set} */ + let allowedTypes = ALL_TYPES; + const failedTypeLocations = []; + + for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/type"]) { + const isValid = normalizedErrors["https://json-schema.org/keyword/type"][schemaLocation]; + if (!isValid) { + failedTypeLocations.push(schemaLocation); + + const keyword = await getSchema(schemaLocation); + /** @type {string|string[]} */ + const value = Schema.value(keyword); + const types = Array.isArray(value) ? value : [value]; + /** @type {Set} */ + const keywordTypes = new Set(types); + if (keywordTypes.has("number")) { + keywordTypes.add("integer"); + } + allowedTypes = allowedTypes.intersection(keywordTypes); + } } - const keyword = await getSchema(schemaLocation); - const expectedTypes = /** @type string[] */ (Schema.typeOf(keyword) === "array" - ? Schema.value(keyword) - : [Schema.value(keyword)]); + if (allowedTypes.has("number")) { + allowedTypes.delete("integer"); + } - errors.push({ - message: localization.getTypeErrorMessage(expectedTypes), - instanceLocation: Instance.uri(instance), - schemaLocations: [schemaLocation] - }); + if (allowedTypes.size === 0) { + errors.push({ + message: localization.getBooleanSchemaErrorMessage(), + instanceLocation: Instance.uri(instance), + schemaLocations: failedTypeLocations + }); + } else if (failedTypeLocations.length > 0) { + errors.push({ + message: localization.getTypeErrorMessage([...allowedTypes]), + instanceLocation: Instance.uri(instance), + schemaLocations: failedTypeLocations + }); + } } return errors; diff --git a/src/test-suite/tests/type.json b/src/test-suite/tests/type.json index 94cf934..fcaf923 100644 --- a/src/test-suite/tests/type.json +++ b/src/test-suite/tests/type.json @@ -36,6 +36,44 @@ "schemaLocations": ["#/type"] } ] + }, + { + "description": "collapsed type errors", + "schema": { + "allOf": [ + { "type": ["string", "boolean", "number"] }, + { "type": ["string", "number"] }, + { "type": "string" } + ] + }, + "instance": null, + "errors": [ + { + "messageId": "type-message", + "messageParams": { + "expectedTypes": { "or": ["string"] } + }, + "instanceLocation": "#", + "schemaLocations": ["#/allOf/0/type", "#/allOf/1/type", "#/allOf/2/type"] + } + ] + }, + { + "description": "no types allowed", + "schema": { + "allOf": [ + { "type": "string" }, + { "type": "number" } + ] + }, + "instance": null, + "errors": [ + { + "messageId": "boolean-schema-message", + "instanceLocation": "#", + "schemaLocations": ["#/allOf/0/type", "#/allOf/1/type"] + } + ] } ] }