From c1c57f8bc2749261c1840a4ff808ff90faaa256b Mon Sep 17 00:00:00 2001 From: Kaileshwar16 Date: Sat, 10 Oct 2026 19:15:58 +0530 Subject: [PATCH 1/2] fix: preserve integer enum and const values in type errors --- src/error-handlers/typeConstEnum.js | 11 ++- src/test-suite/tests/const.json | 35 ++++++++ src/test-suite/tests/enum.json | 126 ++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 2 deletions(-) diff --git a/src/error-handlers/typeConstEnum.js b/src/error-handlers/typeConstEnum.js index 9e9a9f2..d20aa56 100644 --- a/src/error-handlers/typeConstEnum.js +++ b/src/error-handlers/typeConstEnum.js @@ -52,7 +52,7 @@ const typeConstEnumErrorHandler = { const keywordJson = new Set(); const constValueJson = /** @type string */ (normalizedErrors["https://json-schema.org/keyword/const"][schemaLocation].value); - if (allowedTypes.has(jsonTypeOf(constValueJson))) { + if (isAllowedType(constValueJson, allowedTypes)) { keywordJson.add(constValueJson); } else { typeFiltered = true; @@ -70,7 +70,7 @@ const typeConstEnumErrorHandler = { const keywordJson = new Set(); const enumValuesJson = /** @type string[] */ (normalizedErrors["https://json-schema.org/keyword/enum"][schemaLocation].value); for (const enumValueJson of enumValuesJson) { - if (allowedTypes.has(jsonTypeOf(enumValueJson))) { + if (isAllowedType(enumValueJson, allowedTypes)) { keywordJson.add(enumValueJson); } else { typeFiltered = true; @@ -127,6 +127,13 @@ const typeConstEnumErrorHandler = { } }; +/** @type (json: string, allowedTypes: Set) => boolean */ +const isAllowedType = (json, allowedTypes) => { + const type = jsonTypeOf(json); + return allowedTypes.has(type) + || (type === "number" && allowedTypes.has("integer") && Number.isInteger(JSON.parse(json))); +}; + /** * @param {string} value * @returns {string} diff --git a/src/test-suite/tests/const.json b/src/test-suite/tests/const.json index 8d11e64..536ca07 100644 --- a/src/test-suite/tests/const.json +++ b/src/test-suite/tests/const.json @@ -21,6 +21,41 @@ } ] }, + { + "description": "const with integer type preserves integers", + "compatibility": "6", + "schema": { "type": "integer", "const": 1 }, + "instance": "hello", + "errors": [ + { + "messageId": "const-message", + "messageParams": { "expected": "1" }, + "instanceLocation": "#", + "schemaLocations": ["#/const"] + } + ] + }, + { + "description": "const with integer type rejects fractions", + "compatibility": "6", + "schema": { "type": "integer", "const": 1.5 }, + "instance": "hello", + "errors": [ + { + "messageId": "boolean-schema-message", + "messageParams": {}, + "instanceLocation": "#", + "schemaLocations": ["#/type", "#/const"] + } + ] + }, + { + "description": "const with integer type passes", + "compatibility": "6", + "schema": { "type": "integer", "const": -1 }, + "instance": -1, + "errors": [] + }, { "description": "const pass", "compatibility": "6", diff --git a/src/test-suite/tests/enum.json b/src/test-suite/tests/enum.json index 6f312e2..797554d 100644 --- a/src/test-suite/tests/enum.json +++ b/src/test-suite/tests/enum.json @@ -21,6 +21,132 @@ } ] }, + { + "description": "enum with integer type preserves integers", + "schema": { "type": "integer", "enum": [1, 2] }, + "instance": "hello", + "errors": [ + { + "messageId": "enum-message", + "messageParams": { "expected": { "or": ["1", "2"] }, "count": 2 }, + "instanceLocation": "#", + "schemaLocations": ["#/enum"] + } + ] + }, + { + "description": "enum with integer type filters fractions", + "schema": { "type": "integer", "enum": [1, 1.5] }, + "instance": "hello", + "errors": [ + { + "messageId": "const-message", + "messageParams": { "expected": "1" }, + "instanceLocation": "#", + "schemaLocations": ["#/type", "#/enum"] + } + ] + }, + { + "description": "enum with number type preserves integers and fractions", + "schema": { "type": "number", "enum": [1, 1.5] }, + "instance": "hello", + "errors": [ + { + "messageId": "enum-message", + "messageParams": { "expected": { "or": ["1", "1.5"] }, "count": 2 }, + "instanceLocation": "#", + "schemaLocations": ["#/enum"] + } + ] + }, + { + "description": "enum with string type preserves strings", + "schema": { "type": "string", "enum": ["a", "b"] }, + "instance": 42, + "errors": [ + { + "messageId": "enum-message", + "messageParams": { "expected": { "or": ["\"a\"", "\"b\""] }, "count": 2 }, + "instanceLocation": "#", + "schemaLocations": ["#/enum"] + } + ] + }, + { + "description": "enum with integer and string types filters fractions", + "schema": { "type": ["integer", "string"], "enum": [1, "a", 1.5] }, + "instance": null, + "errors": [ + { + "messageId": "enum-message", + "messageParams": { "expected": { "or": ["1", "\"a\""] }, "count": 2 }, + "instanceLocation": "#", + "schemaLocations": ["#/type", "#/enum"] + } + ] + }, + { + "description": "enum with integer type preserves negative integers and zero at nested locations", + "schema": { + "properties": { + "value": { "type": "integer", "enum": [-2, 0, -1.5] } + } + }, + "instance": { "value": "hello" }, + "errors": [ + { + "messageId": "enum-message", + "messageParams": { "expected": { "or": ["-2", "0"] }, "count": 2 }, + "instanceLocation": "#/value", + "schemaLocations": ["#/properties/value/type", "#/properties/value/enum"] + } + ] + }, + { + "description": "enum with integer type handles exponential notation", + "schema": { "type": "integer", "enum": [1e21, 1e-7] }, + "instance": "hello", + "errors": [ + { + "messageId": "const-message", + "messageParams": { "expected": "1e+21" }, + "instanceLocation": "#", + "schemaLocations": ["#/type", "#/enum"] + } + ] + }, + { + "description": "enum with integer type rejects fractions and numeric strings", + "schema": { "type": "integer", "enum": [1.5, "1"] }, + "instance": "hello", + "errors": [ + { + "messageId": "boolean-schema-message", + "messageParams": {}, + "instanceLocation": "#", + "schemaLocations": ["#/type", "#/enum"] + } + ] + }, + { + "description": "enum with integer type passes", + "schema": { "type": "integer", "enum": [-2, 0, 1.5] }, + "instance": 0, + "errors": [] + }, + { + "description": "enum with number type passes for fractions", + "schema": { "type": "number", "enum": [1, 1.5] }, + "instance": 1.5, + "errors": [] + }, + { + "description": "enum with integer and string types passes for strings", + "schema": { "type": ["integer", "string"], "enum": [1, "a", 1.5] }, + "instance": "a", + "errors": [] + }, { "description": "enum pass", "schema": { From a8a8c5715ba6cc8b0fc90ecd1cb183db5cc78480 Mon Sep 17 00:00:00 2001 From: Jason Desrosiers Date: Sun, 11 Oct 2026 21:45:23 -0700 Subject: [PATCH 2/2] test: cover integer enum filtering with number type across allOf Co-Authored-By: Claude Opus 5.5 --- src/test-suite/tests/enum.json | 36 ++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/test-suite/tests/enum.json b/src/test-suite/tests/enum.json index 797554d..8f661a5 100644 --- a/src/test-suite/tests/enum.json +++ b/src/test-suite/tests/enum.json @@ -86,6 +86,42 @@ } ] }, + { + "description": "enum with integer type filters fractions when combined with number type", + "schema": { + "allOf": [ + { "type": "number" }, + { "type": "integer", "enum": [1, 1.5] } + ] + }, + "instance": "hello", + "errors": [ + { + "messageId": "const-message", + "messageParams": { "expected": "1" }, + "instanceLocation": "#", + "schemaLocations": ["#/allOf/0/type", "#/allOf/1/type", "#/allOf/1/enum"] + } + ] + }, + { + "description": "enum with number type filters fractions when combined with integer type", + "schema": { + "allOf": [ + { "type": "integer" }, + { "type": "number", "enum": [1, 1.5] } + ] + }, + "instance": "hello", + "errors": [ + { + "messageId": "const-message", + "messageParams": { "expected": "1" }, + "instanceLocation": "#", + "schemaLocations": ["#/allOf/0/type", "#/allOf/1/type", "#/allOf/1/enum"] + } + ] + }, { "description": "enum with integer type preserves negative integers and zero at nested locations", "schema": {