Skip to content

Commit d15b71a

Browse files
committed
Implement the Basic output format
1 parent 018aedc commit d15b71a

File tree

2 files changed

+1178
-39
lines changed

2 files changed

+1178
-39
lines changed

src/index.js

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,17 @@ import {
1818

1919
/**
2020
* @typedef {{
21-
* valid: boolean;
21+
* instanceLocation: string;
22+
* absoluteKeywordLocation: string;
23+
* keywordLocation?: string;
24+
* error?: string;
25+
* }} OutputUnit
26+
*
27+
* @typedef {{
28+
* valid: true;
29+
* } | {
30+
* valid: false;
31+
* errors?: OutputUnit[];
2232
* }} Output
2333
*/
2434

@@ -39,26 +49,44 @@ export const validate = (schema, instance) => {
3949
}
4050
}
4151

42-
const valid = validateSchema(schemaNode, toJsonNode(instance));
52+
/** @type OutputUnit[] */
53+
const errors = [];
54+
const valid = validateSchema(schemaNode, toJsonNode(instance), errors);
4355

4456
schemaRegistry.delete(uri);
4557

46-
return { valid };
58+
return valid ? { valid } : { valid, errors };
4759
};
4860

49-
/** @type (schemaNode: JsonNode, instanceNode: JsonNode) => boolean */
50-
const validateSchema = (schemaNode, instanceNode) => {
61+
/** @type (schemaNode: JsonNode, instanceNode: JsonNode, errors: OutputUnit[]) => boolean */
62+
const validateSchema = (schemaNode, instanceNode, errors) => {
5163
if (schemaNode.type === "json") {
5264
switch (schemaNode.jsonType) {
5365
case "boolean":
66+
if (!schemaNode.value) {
67+
errors.push({
68+
absoluteKeywordLocation: schemaNode.location,
69+
instanceLocation: instanceNode.location
70+
});
71+
}
5472
return schemaNode.value;
73+
5574
case "object":
5675
let isValid = true;
5776
for (const propertyNode of schemaNode.children) {
5877
const [keywordNode, keywordValueNode] = propertyNode.children;
5978
const keywordHandler = keywordHandlers.get(keywordNode.value);
60-
if (keywordHandler && !keywordHandler(keywordValueNode, instanceNode, schemaNode)) {
61-
isValid = false;
79+
if (keywordHandler) {
80+
/** @type OutputUnit[] */
81+
const keywordErrors = [];
82+
if (!keywordHandler(keywordValueNode, instanceNode, schemaNode, keywordErrors)) {
83+
isValid = false;
84+
errors.push({
85+
absoluteKeywordLocation: keywordValueNode.location,
86+
instanceLocation: instanceNode.location
87+
});
88+
errors.push(...keywordErrors);
89+
}
6290
}
6391
}
6492

@@ -81,14 +109,15 @@ export const registerSchema = (schema, uri) => {
81109
* @typedef {(
82110
* keywordNode: JsonNode,
83111
* instanceNode: JsonNode,
84-
* schemaNode: JsonObjectNode
112+
* schemaNode: JsonObjectNode,
113+
* errors: OutputUnit[],
85114
* ) => boolean} KeywordHandler
86115
*/
87116

88117
/** @type Map<string, KeywordHandler> */
89118
const keywordHandlers = new Map();
90119

91-
keywordHandlers.set("$ref", (refNode, instanceNode) => {
120+
keywordHandlers.set("$ref", (refNode, instanceNode, _schemaNode, errors) => {
92121
assertNodeType(refNode, "string");
93122

94123
const uri = refNode.location.startsWith("#")
@@ -103,10 +132,10 @@ keywordHandlers.set("$ref", (refNode, instanceNode) => {
103132
const pointer = decodeURI(parseIriReference(refNode.value).fragment ?? "");
104133
const referencedSchemaNode = jsonPointerGet(pointer, schemaNode, uri);
105134

106-
return validateSchema(referencedSchemaNode, instanceNode);
135+
return validateSchema(referencedSchemaNode, instanceNode, errors);
107136
});
108137

109-
keywordHandlers.set("additionalProperties", (additionalPropertiesNode, instanceNode, schemaNode) => {
138+
keywordHandlers.set("additionalProperties", (additionalPropertiesNode, instanceNode, schemaNode, errors) => {
110139
if (instanceNode.jsonType !== "object") {
111140
return true;
112141
}
@@ -134,7 +163,7 @@ keywordHandlers.set("additionalProperties", (additionalPropertiesNode, instanceN
134163
let isValid = true;
135164
for (const propertyNode of instanceNode.children) {
136165
const [propertyNameNode, instancePropertyNode] = propertyNode.children;
137-
if (!isDefinedProperty.test(propertyNameNode.value) && !validateSchema(additionalPropertiesNode, instancePropertyNode)) {
166+
if (!isDefinedProperty.test(propertyNameNode.value) && !validateSchema(additionalPropertiesNode, instancePropertyNode, errors)) {
138167
isValid = false;
139168
}
140169
}
@@ -147,37 +176,38 @@ const regexEscape = (string) => string
147176
.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&")
148177
.replace(/-/g, "\\x2d");
149178

150-
keywordHandlers.set("allOf", (allOfNode, instanceNode) => {
179+
keywordHandlers.set("allOf", (allOfNode, instanceNode, _schemaNode, errors) => {
151180
assertNodeType(allOfNode, "array");
152181

153182
let isValid = true;
154183
for (const schemaNode of allOfNode.children) {
155-
if (!validateSchema(schemaNode, instanceNode)) {
184+
if (!validateSchema(schemaNode, instanceNode, errors)) {
156185
isValid = false;
157186
}
158187
}
159188

160189
return isValid;
161190
});
162191

163-
keywordHandlers.set("anyOf", (anyOfNode, instanceNode) => {
192+
keywordHandlers.set("anyOf", (anyOfNode, instanceNode, _schemaNode, errors) => {
164193
assertNodeType(anyOfNode, "array");
165194

166195
let isValid = false;
167196
for (const schemaNode of anyOfNode.children) {
168-
if (validateSchema(schemaNode, instanceNode)) {
197+
if (validateSchema(schemaNode, instanceNode, errors)) {
169198
isValid = true;
170199
}
171200
}
201+
172202
return isValid;
173203
});
174204

175-
keywordHandlers.set("oneOf", (oneOfNode, instanceNode) => {
205+
keywordHandlers.set("oneOf", (oneOfNode, instanceNode, _schemaNode, errors) => {
176206
assertNodeType(oneOfNode, "array");
177207

178208
let matches = 0;
179209
for (const schemaNode of oneOfNode.children) {
180-
if (validateSchema(schemaNode, instanceNode)) {
210+
if (validateSchema(schemaNode, instanceNode, errors)) {
181211
matches++;
182212
}
183213
}
@@ -186,10 +216,10 @@ keywordHandlers.set("oneOf", (oneOfNode, instanceNode) => {
186216
});
187217

188218
keywordHandlers.set("not", (notNode, instanceNode) => {
189-
return !validateSchema(notNode, instanceNode);
219+
return !validateSchema(notNode, instanceNode, []);
190220
});
191221

192-
keywordHandlers.set("contains", (containsNode, instanceNode, schemaNode) => {
222+
keywordHandlers.set("contains", (containsNode, instanceNode, schemaNode, errors) => {
193223
if (instanceNode.jsonType !== "array") {
194224
return true;
195225
}
@@ -212,15 +242,15 @@ keywordHandlers.set("contains", (containsNode, instanceNode, schemaNode) => {
212242

213243
let matches = 0;
214244
for (const itemNode of instanceNode.children) {
215-
if (validateSchema(containsNode, itemNode)) {
245+
if (validateSchema(containsNode, itemNode, errors)) {
216246
matches++;
217247
}
218248
}
219249

220250
return matches >= minContains && matches <= maxContains;
221251
});
222252

223-
keywordHandlers.set("dependentSchemas", (dependentSchemasNode, instanceNode) => {
253+
keywordHandlers.set("dependentSchemas", (dependentSchemasNode, instanceNode, _schemaNode, errors) => {
224254
if (instanceNode.jsonType !== "object") {
225255
return true;
226256
}
@@ -230,37 +260,37 @@ keywordHandlers.set("dependentSchemas", (dependentSchemasNode, instanceNode) =>
230260
let isValid = true;
231261
for (const propertyNode of dependentSchemasNode.children) {
232262
const [keyNode, schemaNode] = propertyNode.children;
233-
if (jsonObjectHas(keyNode.value, instanceNode) && !validateSchema(schemaNode, instanceNode)) {
263+
if (jsonObjectHas(keyNode.value, instanceNode) && !validateSchema(schemaNode, instanceNode, errors)) {
234264
isValid = false;
235265
}
236266
}
237267

238268
return isValid;
239269
});
240270

241-
keywordHandlers.set("then", (thenNode, instanceNode, schemaNode) => {
271+
keywordHandlers.set("then", (thenNode, instanceNode, schemaNode, errors) => {
242272
if (jsonObjectHas("if", schemaNode)) {
243273
const ifNode = jsonPointerStep("if", schemaNode);
244-
if (validateSchema(ifNode, instanceNode)) {
245-
return validateSchema(thenNode, instanceNode);
274+
if (validateSchema(ifNode, instanceNode, [])) {
275+
return validateSchema(thenNode, instanceNode, errors);
246276
}
247277
}
248278

249279
return true;
250280
});
251281

252-
keywordHandlers.set("else", (elseNode, instanceNode, schemaNode) => {
282+
keywordHandlers.set("else", (elseNode, instanceNode, schemaNode, errors) => {
253283
if (jsonObjectHas("if", schemaNode)) {
254284
const ifNode = jsonPointerStep("if", schemaNode);
255-
if (!validateSchema(ifNode, instanceNode)) {
256-
return validateSchema(elseNode, instanceNode);
285+
if (!validateSchema(ifNode, instanceNode, [])) {
286+
return validateSchema(elseNode, instanceNode, errors);
257287
}
258288
}
259289

260290
return true;
261291
});
262292

263-
keywordHandlers.set("items", (itemsNode, instanceNode, schemaNode) => {
293+
keywordHandlers.set("items", (itemsNode, instanceNode, schemaNode, errors) => {
264294
if (instanceNode.jsonType !== "array") {
265295
return true;
266296
}
@@ -275,15 +305,15 @@ keywordHandlers.set("items", (itemsNode, instanceNode, schemaNode) => {
275305

276306
let isValid = true;
277307
for (const itemNode of instanceNode.children.slice(numberOfPrefixItems)) {
278-
if (!validateSchema(itemsNode, itemNode)) {
308+
if (!validateSchema(itemsNode, itemNode, errors)) {
279309
isValid = false;
280310
}
281311
}
282312

283313
return isValid;
284314
});
285315

286-
keywordHandlers.set("patternProperties", (patternPropertiesNode, instanceNode) => {
316+
keywordHandlers.set("patternProperties", (patternPropertiesNode, instanceNode, _schemaNode, errors) => {
287317
if (instanceNode.jsonType !== "object") {
288318
return true;
289319
}
@@ -297,7 +327,7 @@ keywordHandlers.set("patternProperties", (patternPropertiesNode, instanceNode) =
297327
for (const propertyNode of instanceNode.children) {
298328
const [propertyNameNode, propertyValueNode] = propertyNode.children;
299329
const propertyName = propertyNameNode.value;
300-
if (pattern.test(propertyName) && !validateSchema(patternSchemaNode, propertyValueNode)) {
330+
if (pattern.test(propertyName) && !validateSchema(patternSchemaNode, propertyValueNode, errors)) {
301331
isValid = false;
302332
}
303333
}
@@ -306,7 +336,7 @@ keywordHandlers.set("patternProperties", (patternPropertiesNode, instanceNode) =
306336
return isValid;
307337
});
308338

309-
keywordHandlers.set("prefixItems", (prefixItemsNode, instanceNode) => {
339+
keywordHandlers.set("prefixItems", (prefixItemsNode, instanceNode, _schemaNode, errors) => {
310340
if (instanceNode.jsonType !== "array") {
311341
return true;
312342
}
@@ -315,15 +345,15 @@ keywordHandlers.set("prefixItems", (prefixItemsNode, instanceNode) => {
315345

316346
let isValid = true;
317347
for (let index = 0; index < instanceNode.children.length; index++) {
318-
if (prefixItemsNode.children[index] && !validateSchema(prefixItemsNode.children[index], instanceNode.children[index])) {
348+
if (prefixItemsNode.children[index] && !validateSchema(prefixItemsNode.children[index], instanceNode.children[index], errors)) {
319349
isValid = false;
320350
}
321351
}
322352

323353
return isValid;
324354
});
325355

326-
keywordHandlers.set("properties", (propertiesNode, instanceNode) => {
356+
keywordHandlers.set("properties", (propertiesNode, instanceNode, _schemaNode, errors) => {
327357
if (instanceNode.jsonType !== "object") {
328358
return true;
329359
}
@@ -335,7 +365,7 @@ keywordHandlers.set("properties", (propertiesNode, instanceNode) => {
335365
const [propertyNameNode, instancePropertyNode] = jsonPropertyNode.children;
336366
if (jsonObjectHas(propertyNameNode.value, propertiesNode)) {
337367
const schemaPropertyNode = jsonPointerStep(propertyNameNode.value, propertiesNode);
338-
if (!validateSchema(schemaPropertyNode, instancePropertyNode)) {
368+
if (!validateSchema(schemaPropertyNode, instancePropertyNode, errors)) {
339369
isValid = false;
340370
}
341371
}
@@ -344,7 +374,7 @@ keywordHandlers.set("properties", (propertiesNode, instanceNode) => {
344374
return isValid;
345375
});
346376

347-
keywordHandlers.set("propertyNames", (propertyNamesNode, instanceNode) => {
377+
keywordHandlers.set("propertyNames", (propertyNamesNode, instanceNode, _schemaNode, errors) => {
348378
if (instanceNode.jsonType !== "object") {
349379
return true;
350380
}
@@ -358,7 +388,7 @@ keywordHandlers.set("propertyNames", (propertyNamesNode, instanceNode) => {
358388
value: propertyNode.children[0].value,
359389
location: JsonPointer.append(propertyNode.children[0].value, instanceNode.location)
360390
};
361-
if (!validateSchema(propertyNamesNode, keyNode)) {
391+
if (!validateSchema(propertyNamesNode, keyNode, errors)) {
362392
isValid = false;
363393
}
364394
}

0 commit comments

Comments
 (0)