Skip to content

Commit d610291

Browse files
Sam-61sjdesrosiers
andauthored
Combine const/enum with type error messages (#169)
* combined const/enum with type error messages * type and constEnum handlers are combined * simplified the handler * refactored handler to filter during collection * suggested cleanup * Final cleanup --------- Co-authored-by: Jason Desrosiers <jdesrosi@gmail.com>
1 parent 13babea commit d610291

6 files changed

Lines changed: 228 additions & 143 deletions

File tree

src/error-handlers/constEnum.js

Lines changed: 0 additions & 78 deletions
This file was deleted.

src/error-handlers/type.js

Lines changed: 0 additions & 61 deletions
This file was deleted.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { getSchema } from "@hyperjump/json-schema/experimental";
2+
import * as Schema from "@hyperjump/browser";
3+
import * as Instance from "@hyperjump/json-schema/instance/experimental";
4+
import jsonStringify from "json-stringify-deterministic";
5+
6+
/**
7+
* @import { ErrorHandler, Json } from "../index.d.ts"
8+
*/
9+
10+
const ALL_TYPES = new Set(["null", "boolean", "number", "string", "array", "object", "integer"]);
11+
12+
/** @type {ErrorHandler} */
13+
const typeConstEnumErrorHandler = async (normalizedErrors, instance, localization) => {
14+
let allowedTypes = new Set(ALL_TYPES);
15+
/** @type {string[]} */
16+
const failedTypeLocations = [];
17+
18+
for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/type"]) {
19+
if (!normalizedErrors["https://json-schema.org/keyword/type"][schemaLocation]) {
20+
failedTypeLocations.push(schemaLocation);
21+
22+
const keyword = await getSchema(schemaLocation);
23+
/** @type {string | string[]} */
24+
const value = Schema.value(keyword);
25+
const types = Array.isArray(value) ? value : [value];
26+
/** @type {Set<string>} */
27+
const keywordTypes = new Set(types);
28+
if (keywordTypes.has("number")) {
29+
keywordTypes.add("integer");
30+
}
31+
allowedTypes = allowedTypes.intersection(keywordTypes);
32+
}
33+
}
34+
if (allowedTypes.has("number")) {
35+
allowedTypes.delete("integer");
36+
}
37+
38+
/** @type {Set<string> | undefined} */
39+
let allowedJson;
40+
41+
/** @type {string[]} */
42+
const constEnumLocations = [];
43+
/** @type {string[]} */
44+
const failedConstLocations = [];
45+
/** @type {string[]} */
46+
const failedEnumLocations = [];
47+
let typeFiltered = false;
48+
49+
for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/const"]) {
50+
constEnumLocations.push(schemaLocation);
51+
if (!normalizedErrors["https://json-schema.org/keyword/const"][schemaLocation]) {
52+
failedConstLocations.push(schemaLocation);
53+
}
54+
55+
const keyword = await getSchema(schemaLocation);
56+
const keywordJson = new Set();
57+
if (allowedTypes.has(Schema.typeOf(keyword))) {
58+
keywordJson.add(jsonStringify(Schema.value(keyword)));
59+
} else {
60+
typeFiltered = true;
61+
}
62+
63+
allowedJson = allowedJson?.intersection(keywordJson) ?? keywordJson;
64+
}
65+
66+
for (const schemaLocation in normalizedErrors["https://json-schema.org/keyword/enum"]) {
67+
constEnumLocations.push(schemaLocation);
68+
if (!normalizedErrors["https://json-schema.org/keyword/enum"][schemaLocation]) {
69+
failedEnumLocations.push(schemaLocation);
70+
}
71+
72+
const keyword = await getSchema(schemaLocation);
73+
const keywordJson = new Set();
74+
for await (const enumValueNode of Schema.iter(keyword)) {
75+
if (allowedTypes.has(Schema.typeOf(enumValueNode))) {
76+
keywordJson.add(jsonStringify(Schema.value(enumValueNode)));
77+
} else {
78+
typeFiltered = true;
79+
}
80+
}
81+
82+
allowedJson = allowedJson?.intersection(keywordJson) ?? keywordJson;
83+
}
84+
85+
const failedLocations = failedConstLocations.length > 0
86+
? failedConstLocations
87+
: failedEnumLocations;
88+
89+
if (failedLocations.length === 0 && failedTypeLocations.length === 0) {
90+
return [];
91+
} else if (allowedTypes.size === 0 || allowedJson?.size === 0) {
92+
return [{
93+
message: localization.getBooleanSchemaErrorMessage(),
94+
instanceLocation: Instance.uri(instance),
95+
schemaLocations: [...failedTypeLocations, ...constEnumLocations]
96+
}];
97+
} else if (allowedJson?.size) {
98+
/** @type Json[] */
99+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
100+
const allowedValues = [...allowedJson ?? []].map((json) => JSON.parse(json));
101+
102+
return [{
103+
message: localization.getEnumErrorMessage(allowedValues),
104+
instanceLocation: Instance.uri(instance),
105+
schemaLocations: typeFiltered
106+
? [...failedTypeLocations, ...constEnumLocations]
107+
: failedLocations
108+
}];
109+
} else {
110+
return [{
111+
message: localization.getTypeErrorMessage([...allowedTypes]),
112+
instanceLocation: Instance.uri(instance),
113+
schemaLocations: failedTypeLocations
114+
}];
115+
}
116+
};
117+
118+
export default typeConstEnumErrorHandler;

src/index.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ import unknownNormalizationHandler from "./normalization-handlers/unknown.js";
5555
// Error Handlers
5656
import anyOfErrorHandler from "./error-handlers/anyOf.js";
5757
import booleanSchemaErrorHandler from "./error-handlers/boolean-schema.js";
58-
import constEnumErrorHandler from "./error-handlers/constEnum.js";
5958
import containsErrorHandler from "./error-handlers/contains.js";
6059
import dependenciesErrorHandler from "./error-handlers/draft-04/dependencies.js";
6160
import formatErrorHandler from "./error-handlers/format.js";
@@ -72,7 +71,7 @@ import notErrorHandler from "./error-handlers/not.js";
7271
import oneOfErrorHandler from "./error-handlers/oneOf.js";
7372
import patternErrorHandler from "./error-handlers/pattern.js";
7473
import requiredErrorHandler from "./error-handlers/required.js";
75-
import typeErrorHandler from "./error-handlers/type.js";
74+
import typeConstEnumErrorHandler from "./error-handlers/typeConstEnum.js";
7675
import uniqueItemsErrorHandler from "./error-handlers/uniqueItems.js";
7776
import unknownErrorHandler from "./error-handlers/unknown.js";
7877

@@ -135,7 +134,6 @@ setNormalizationHandler("https://json-schema.org/keyword/unknown", unknownNormal
135134

136135
addErrorHandler(anyOfErrorHandler);
137136
addErrorHandler(booleanSchemaErrorHandler);
138-
addErrorHandler(constEnumErrorHandler);
139137
addErrorHandler(containsErrorHandler);
140138
addErrorHandler(dependenciesErrorHandler);
141139
addErrorHandler(formatErrorHandler);
@@ -152,7 +150,7 @@ addErrorHandler(notErrorHandler);
152150
addErrorHandler(oneOfErrorHandler);
153151
addErrorHandler(patternErrorHandler);
154152
addErrorHandler(requiredErrorHandler);
155-
addErrorHandler(typeErrorHandler);
153+
addErrorHandler(typeConstEnumErrorHandler);
156154
addErrorHandler(uniqueItemsErrorHandler);
157155
addErrorHandler(unknownErrorHandler);
158156

src/test-suite/tests/const.json

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,49 @@
9292
]
9393
}
9494
]
95+
},
96+
{
97+
"description": "const with type - filter by type",
98+
"compatibility": "6",
99+
"schema": {
100+
"allOf": [
101+
{ "type": "number" },
102+
{ "const": "foo" }
103+
]
104+
},
105+
"instance": "foo",
106+
"errors": [
107+
{
108+
"messageId": "boolean-schema-message",
109+
"messageParams": {},
110+
"instanceLocation": "#",
111+
"schemaLocations": [
112+
"#/allOf/0/type",
113+
"#/allOf/1/const"
114+
]
115+
}
116+
]
117+
},
118+
{
119+
"description": "const with matching type",
120+
"compatibility": "6",
121+
"schema": {
122+
"allOf": [
123+
{ "type": "string" },
124+
{ "const": "foo" }
125+
]
126+
},
127+
"instance": 42,
128+
"errors": [
129+
{
130+
"messageId": "const-message",
131+
"messageParams": {
132+
"expected": "\"foo\""
133+
},
134+
"instanceLocation": "#",
135+
"schemaLocations": ["#/allOf/1/const"]
136+
}
137+
]
95138
}
96139
]
97140
}

0 commit comments

Comments
 (0)