Skip to content

Commit 237ff45

Browse files
committed
New normalized output implementation
1 parent 8b60f29 commit 237ff45

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed

src/basic-to-detailed.js

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import { registerSchema, validate } from "@hyperjump/json-schema/draft-2020-12";
2+
import { BASIC, compile, getSchema } from "@hyperjump/json-schema/experimental";
3+
import * as Instance from "@hyperjump/json-schema/instance/experimental";
4+
5+
/**
6+
* @import { OutputUnit } from "@hyperjump/json-schema"
7+
* @import { AST } from "@hyperjump/json-schema/experimental"
8+
* @import { JsonNode } from "@hyperjump/json-schema/instance/experimental"
9+
*/
10+
11+
/**
12+
* @typedef {{
13+
* [instanceLocation: string]: {
14+
* [keywordUri: string]: {
15+
* [keywordLocation: string]: boolean | {
16+
* [schemaLocation: string]: NormalizedOutput
17+
* }
18+
* }
19+
* }
20+
* }} NormalizedOutput
21+
*/
22+
23+
/** @type (schemaLocation: string, ast: AST, instance: JsonNode, errorIndex: ErrorIndex) => NormalizedOutput */
24+
const evaluateSchema = (schemaLocation, ast, instance, errorIndex) => {
25+
const instanceLocation = Instance.uri(instance);
26+
const schemaNode = ast[schemaLocation];
27+
28+
if (typeof schemaNode === "boolean") {
29+
return {
30+
[instanceLocation]: {
31+
"https://json-schema.org/validation": {
32+
[schemaLocation]: schemaNode
33+
}
34+
}
35+
};
36+
}
37+
38+
/** @type NormalizedOutput */
39+
const output = { [instanceLocation]: {} };
40+
for (const [keywordUri, keywordLocation, keywordValue] of schemaNode) {
41+
const keyword = keywordHandlers[keywordUri] ?? {};
42+
43+
const keywordOutput = keyword.evaluate?.(keywordValue, ast, instance, errorIndex);
44+
if (keyword.simpleApplicator) {
45+
for (const subschemaLocation in keywordOutput) {
46+
mergeOutput(output, keywordOutput[subschemaLocation]);
47+
}
48+
} else if (errorIndex[keywordLocation]?.[instanceLocation]) {
49+
output[instanceLocation][keywordUri] ??= {};
50+
output[instanceLocation][keywordUri][keywordLocation] = keywordOutput ?? false;
51+
} else if (keyword.appliesTo?.(Instance.typeOf(instance)) !== false) {
52+
output[instanceLocation][keywordUri] ??= {};
53+
output[instanceLocation][keywordUri][keywordLocation] = !errorIndex[keywordLocation]?.[instanceLocation];
54+
}
55+
}
56+
57+
return output;
58+
};
59+
60+
/** @type (a: NormalizedOutput, b: NormalizedOutput) => void */
61+
const mergeOutput = (a, b) => {
62+
for (const instanceLocation in b) {
63+
for (const keywordUri in b[instanceLocation]) {
64+
a[instanceLocation] ??= {};
65+
a[instanceLocation][keywordUri] ??= {};
66+
67+
Object.assign(a[instanceLocation][keywordUri], b[instanceLocation][keywordUri]);
68+
}
69+
}
70+
};
71+
72+
/**
73+
* @typedef {{
74+
* evaluate?(value: any, ast: AST, instance: JsonNode, errorIndex: ErrorIndex): { [schemaLocation: string]: NormalizedOutput }
75+
* appliesTo?(type: string): boolean;
76+
* simpleApplicator?: true;
77+
* }} KeywordHandler
78+
*/
79+
80+
/** @type Record<string, KeywordHandler> */
81+
const keywordHandlers = {};
82+
83+
keywordHandlers["https://json-schema.org/keyword/anyOf"] = {
84+
evaluate(/** @type string[] */ anyOf, ast, instance, errorIndex) {
85+
/** @type {{ [schemaLocation: string]: NormalizedOutput}} */
86+
const errors = {};
87+
88+
for (const schemaLocation of anyOf) {
89+
errors[schemaLocation] = evaluateSchema(schemaLocation, ast, instance, errorIndex);
90+
}
91+
92+
return errors;
93+
}
94+
};
95+
96+
keywordHandlers["https://json-schema.org/keyword/oneOf"] = {
97+
evaluate(/** @type string[] */ oneOf, ast, instance, errorIndex) {
98+
/** @type Record<string, NormalizedOutput> */
99+
const errors = {};
100+
101+
for (const schemaLocation of oneOf) {
102+
errors[schemaLocation] = evaluateSchema(schemaLocation, ast, instance, errorIndex);
103+
}
104+
105+
return errors;
106+
}
107+
};
108+
109+
keywordHandlers["https://json-schema.org/keyword/allOf"] = {
110+
evaluate(/** @type string[] */ allOf, ast, instance, errorIndex) {
111+
/** @type Record<string, NormalizedOutput> */
112+
const errors = {};
113+
114+
for (const schemaLocation of allOf) {
115+
errors[schemaLocation] = evaluateSchema(schemaLocation, ast, instance, errorIndex);
116+
}
117+
118+
return errors;
119+
},
120+
simpleApplicator: true
121+
};
122+
123+
keywordHandlers["https://json-schema.org/keyword/ref"] = {
124+
evaluate(/** @type string */ ref, ast, instance, errorIndex) {
125+
return { [ref]: evaluateSchema(ref, ast, instance, errorIndex) };
126+
},
127+
simpleApplicator: true
128+
};
129+
130+
keywordHandlers["https://json-schema.org/keyword/properties"] = {
131+
evaluate(/** @type Record<string, string> */ properties, ast, instance, errorIndex) {
132+
/** @type Record<string, NormalizedOutput> */
133+
const errors = {};
134+
135+
for (const propertyName in properties) {
136+
const propertyNode = Instance.step(propertyName, instance);
137+
if (!propertyNode) {
138+
continue;
139+
}
140+
141+
errors[properties[propertyName]] = evaluateSchema(properties[propertyName], ast, propertyNode, errorIndex);
142+
}
143+
144+
return errors;
145+
},
146+
simpleApplicator: true
147+
};
148+
149+
keywordHandlers["https://json-schema.org/keyword/definitions"] = {
150+
appliesTo() {
151+
return false;
152+
}
153+
};
154+
155+
keywordHandlers["https://json-schema.org/keyword/minLength"] = {
156+
appliesTo(type) {
157+
return type === "string";
158+
}
159+
};
160+
161+
keywordHandlers["https://json-schema.org/keyword/minimum"] = {
162+
appliesTo(type) {
163+
return type === "number";
164+
}
165+
};
166+
167+
/** @typedef {Record<string, Record<string, true>>} ErrorIndex */
168+
169+
/** @type (basicOutput: OutputUnit) => ErrorIndex */
170+
const constructErrorIndex = (basicOutput) => {
171+
/** @type ErrorIndex */
172+
const errorIndex = {};
173+
174+
if (basicOutput.valid) {
175+
return errorIndex;
176+
}
177+
178+
for (const error of /** @type OutputUnit[] */ (basicOutput.errors)) {
179+
errorIndex[error.absoluteKeywordLocation] ??= {};
180+
errorIndex[error.absoluteKeywordLocation][error.instanceLocation] = true;
181+
}
182+
183+
return errorIndex;
184+
};
185+
186+
///////////////////////////////////////////////////////////////////////////////
187+
188+
const subjectUri = "https://example.com/main";
189+
190+
registerSchema({
191+
$schema: "https://json-schema.org/draft/2020-12/schema",
192+
193+
// type: "object",
194+
// properties: {
195+
// foo: false,
196+
// bar: true
197+
// }
198+
199+
// type: "number",
200+
// allOf: [
201+
// { minimum: 5 },
202+
// { minimum: 3 }
203+
// ]
204+
205+
// allOf: [
206+
// true,
207+
// false
208+
// ],
209+
210+
type: "object",
211+
properties: {
212+
foo: {
213+
anyOf: [
214+
{ $ref: "#/$defs/stringSchema" },
215+
{ $ref: "#/$defs/numberSchema" }
216+
]
217+
},
218+
bar: { type: "boolean" }
219+
},
220+
221+
$defs: {
222+
stringSchema: {
223+
type: "string",
224+
minLength: 5
225+
},
226+
numberSchema: {
227+
type: "number",
228+
minimum: 10
229+
}
230+
}
231+
}, subjectUri);
232+
233+
const instance = { foo: 2 };
234+
235+
const basicOutput = await validate(subjectUri, instance, BASIC);
236+
const errorIndex = constructErrorIndex(basicOutput);
237+
238+
const schema = await getSchema(subjectUri);
239+
const { schemaUri, ast } = await compile(schema);
240+
const value = Instance.fromJs(instance);
241+
const normalizedOutput = evaluateSchema(schemaUri, ast, value, errorIndex);
242+
console.log(JSON.stringify(normalizedOutput, null, " "));

0 commit comments

Comments
 (0)