@@ -18,7 +18,17 @@ import {
18
18
19
19
/**
20
20
* @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[];
22
32
* }} Output
23
33
*/
24
34
@@ -39,26 +49,44 @@ export const validate = (schema, instance) => {
39
49
}
40
50
}
41
51
42
- const valid = validateSchema ( schemaNode , toJsonNode ( instance ) ) ;
52
+ /** @type OutputUnit[] */
53
+ const errors = [ ] ;
54
+ const valid = validateSchema ( schemaNode , toJsonNode ( instance ) , errors ) ;
43
55
44
56
schemaRegistry . delete ( uri ) ;
45
57
46
- return { valid } ;
58
+ return valid ? { valid } : { valid , errors } ;
47
59
} ;
48
60
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 ) => {
51
63
if ( schemaNode . type === "json" ) {
52
64
switch ( schemaNode . jsonType ) {
53
65
case "boolean" :
66
+ if ( ! schemaNode . value ) {
67
+ errors . push ( {
68
+ absoluteKeywordLocation : schemaNode . location ,
69
+ instanceLocation : instanceNode . location
70
+ } ) ;
71
+ }
54
72
return schemaNode . value ;
73
+
55
74
case "object" :
56
75
let isValid = true ;
57
76
for ( const propertyNode of schemaNode . children ) {
58
77
const [ keywordNode , keywordValueNode ] = propertyNode . children ;
59
78
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
+ }
62
90
}
63
91
}
64
92
@@ -81,14 +109,15 @@ export const registerSchema = (schema, uri) => {
81
109
* @typedef {(
82
110
* keywordNode: JsonNode,
83
111
* instanceNode: JsonNode,
84
- * schemaNode: JsonObjectNode
112
+ * schemaNode: JsonObjectNode,
113
+ * errors: OutputUnit[],
85
114
* ) => boolean} KeywordHandler
86
115
*/
87
116
88
117
/** @type Map<string, KeywordHandler> */
89
118
const keywordHandlers = new Map ( ) ;
90
119
91
- keywordHandlers . set ( "$ref" , ( refNode , instanceNode ) => {
120
+ keywordHandlers . set ( "$ref" , ( refNode , instanceNode , _schemaNode , errors ) => {
92
121
assertNodeType ( refNode , "string" ) ;
93
122
94
123
const uri = refNode . location . startsWith ( "#" )
@@ -103,10 +132,10 @@ keywordHandlers.set("$ref", (refNode, instanceNode) => {
103
132
const pointer = decodeURI ( parseIriReference ( refNode . value ) . fragment ?? "" ) ;
104
133
const referencedSchemaNode = jsonPointerGet ( pointer , schemaNode , uri ) ;
105
134
106
- return validateSchema ( referencedSchemaNode , instanceNode ) ;
135
+ return validateSchema ( referencedSchemaNode , instanceNode , errors ) ;
107
136
} ) ;
108
137
109
- keywordHandlers . set ( "additionalProperties" , ( additionalPropertiesNode , instanceNode , schemaNode ) => {
138
+ keywordHandlers . set ( "additionalProperties" , ( additionalPropertiesNode , instanceNode , schemaNode , errors ) => {
110
139
if ( instanceNode . jsonType !== "object" ) {
111
140
return true ;
112
141
}
@@ -134,7 +163,7 @@ keywordHandlers.set("additionalProperties", (additionalPropertiesNode, instanceN
134
163
let isValid = true ;
135
164
for ( const propertyNode of instanceNode . children ) {
136
165
const [ propertyNameNode , instancePropertyNode ] = propertyNode . children ;
137
- if ( ! isDefinedProperty . test ( propertyNameNode . value ) && ! validateSchema ( additionalPropertiesNode , instancePropertyNode ) ) {
166
+ if ( ! isDefinedProperty . test ( propertyNameNode . value ) && ! validateSchema ( additionalPropertiesNode , instancePropertyNode , errors ) ) {
138
167
isValid = false ;
139
168
}
140
169
}
@@ -147,37 +176,38 @@ const regexEscape = (string) => string
147
176
. replace ( / [ | \\ { } ( ) [ \] ^ $ + * ? . ] / g, "\\$&" )
148
177
. replace ( / - / g, "\\x2d" ) ;
149
178
150
- keywordHandlers . set ( "allOf" , ( allOfNode , instanceNode ) => {
179
+ keywordHandlers . set ( "allOf" , ( allOfNode , instanceNode , _schemaNode , errors ) => {
151
180
assertNodeType ( allOfNode , "array" ) ;
152
181
153
182
let isValid = true ;
154
183
for ( const schemaNode of allOfNode . children ) {
155
- if ( ! validateSchema ( schemaNode , instanceNode ) ) {
184
+ if ( ! validateSchema ( schemaNode , instanceNode , errors ) ) {
156
185
isValid = false ;
157
186
}
158
187
}
159
188
160
189
return isValid ;
161
190
} ) ;
162
191
163
- keywordHandlers . set ( "anyOf" , ( anyOfNode , instanceNode ) => {
192
+ keywordHandlers . set ( "anyOf" , ( anyOfNode , instanceNode , _schemaNode , errors ) => {
164
193
assertNodeType ( anyOfNode , "array" ) ;
165
194
166
195
let isValid = false ;
167
196
for ( const schemaNode of anyOfNode . children ) {
168
- if ( validateSchema ( schemaNode , instanceNode ) ) {
197
+ if ( validateSchema ( schemaNode , instanceNode , errors ) ) {
169
198
isValid = true ;
170
199
}
171
200
}
201
+
172
202
return isValid ;
173
203
} ) ;
174
204
175
- keywordHandlers . set ( "oneOf" , ( oneOfNode , instanceNode ) => {
205
+ keywordHandlers . set ( "oneOf" , ( oneOfNode , instanceNode , _schemaNode , errors ) => {
176
206
assertNodeType ( oneOfNode , "array" ) ;
177
207
178
208
let matches = 0 ;
179
209
for ( const schemaNode of oneOfNode . children ) {
180
- if ( validateSchema ( schemaNode , instanceNode ) ) {
210
+ if ( validateSchema ( schemaNode , instanceNode , errors ) ) {
181
211
matches ++ ;
182
212
}
183
213
}
@@ -186,10 +216,10 @@ keywordHandlers.set("oneOf", (oneOfNode, instanceNode) => {
186
216
} ) ;
187
217
188
218
keywordHandlers . set ( "not" , ( notNode , instanceNode ) => {
189
- return ! validateSchema ( notNode , instanceNode ) ;
219
+ return ! validateSchema ( notNode , instanceNode , [ ] ) ;
190
220
} ) ;
191
221
192
- keywordHandlers . set ( "contains" , ( containsNode , instanceNode , schemaNode ) => {
222
+ keywordHandlers . set ( "contains" , ( containsNode , instanceNode , schemaNode , errors ) => {
193
223
if ( instanceNode . jsonType !== "array" ) {
194
224
return true ;
195
225
}
@@ -212,15 +242,15 @@ keywordHandlers.set("contains", (containsNode, instanceNode, schemaNode) => {
212
242
213
243
let matches = 0 ;
214
244
for ( const itemNode of instanceNode . children ) {
215
- if ( validateSchema ( containsNode , itemNode ) ) {
245
+ if ( validateSchema ( containsNode , itemNode , errors ) ) {
216
246
matches ++ ;
217
247
}
218
248
}
219
249
220
250
return matches >= minContains && matches <= maxContains ;
221
251
} ) ;
222
252
223
- keywordHandlers . set ( "dependentSchemas" , ( dependentSchemasNode , instanceNode ) => {
253
+ keywordHandlers . set ( "dependentSchemas" , ( dependentSchemasNode , instanceNode , _schemaNode , errors ) => {
224
254
if ( instanceNode . jsonType !== "object" ) {
225
255
return true ;
226
256
}
@@ -230,37 +260,37 @@ keywordHandlers.set("dependentSchemas", (dependentSchemasNode, instanceNode) =>
230
260
let isValid = true ;
231
261
for ( const propertyNode of dependentSchemasNode . children ) {
232
262
const [ keyNode , schemaNode ] = propertyNode . children ;
233
- if ( jsonObjectHas ( keyNode . value , instanceNode ) && ! validateSchema ( schemaNode , instanceNode ) ) {
263
+ if ( jsonObjectHas ( keyNode . value , instanceNode ) && ! validateSchema ( schemaNode , instanceNode , errors ) ) {
234
264
isValid = false ;
235
265
}
236
266
}
237
267
238
268
return isValid ;
239
269
} ) ;
240
270
241
- keywordHandlers . set ( "then" , ( thenNode , instanceNode , schemaNode ) => {
271
+ keywordHandlers . set ( "then" , ( thenNode , instanceNode , schemaNode , errors ) => {
242
272
if ( jsonObjectHas ( "if" , schemaNode ) ) {
243
273
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 ) ;
246
276
}
247
277
}
248
278
249
279
return true ;
250
280
} ) ;
251
281
252
- keywordHandlers . set ( "else" , ( elseNode , instanceNode , schemaNode ) => {
282
+ keywordHandlers . set ( "else" , ( elseNode , instanceNode , schemaNode , errors ) => {
253
283
if ( jsonObjectHas ( "if" , schemaNode ) ) {
254
284
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 ) ;
257
287
}
258
288
}
259
289
260
290
return true ;
261
291
} ) ;
262
292
263
- keywordHandlers . set ( "items" , ( itemsNode , instanceNode , schemaNode ) => {
293
+ keywordHandlers . set ( "items" , ( itemsNode , instanceNode , schemaNode , errors ) => {
264
294
if ( instanceNode . jsonType !== "array" ) {
265
295
return true ;
266
296
}
@@ -275,15 +305,15 @@ keywordHandlers.set("items", (itemsNode, instanceNode, schemaNode) => {
275
305
276
306
let isValid = true ;
277
307
for ( const itemNode of instanceNode . children . slice ( numberOfPrefixItems ) ) {
278
- if ( ! validateSchema ( itemsNode , itemNode ) ) {
308
+ if ( ! validateSchema ( itemsNode , itemNode , errors ) ) {
279
309
isValid = false ;
280
310
}
281
311
}
282
312
283
313
return isValid ;
284
314
} ) ;
285
315
286
- keywordHandlers . set ( "patternProperties" , ( patternPropertiesNode , instanceNode ) => {
316
+ keywordHandlers . set ( "patternProperties" , ( patternPropertiesNode , instanceNode , _schemaNode , errors ) => {
287
317
if ( instanceNode . jsonType !== "object" ) {
288
318
return true ;
289
319
}
@@ -297,7 +327,7 @@ keywordHandlers.set("patternProperties", (patternPropertiesNode, instanceNode) =
297
327
for ( const propertyNode of instanceNode . children ) {
298
328
const [ propertyNameNode , propertyValueNode ] = propertyNode . children ;
299
329
const propertyName = propertyNameNode . value ;
300
- if ( pattern . test ( propertyName ) && ! validateSchema ( patternSchemaNode , propertyValueNode ) ) {
330
+ if ( pattern . test ( propertyName ) && ! validateSchema ( patternSchemaNode , propertyValueNode , errors ) ) {
301
331
isValid = false ;
302
332
}
303
333
}
@@ -306,7 +336,7 @@ keywordHandlers.set("patternProperties", (patternPropertiesNode, instanceNode) =
306
336
return isValid ;
307
337
} ) ;
308
338
309
- keywordHandlers . set ( "prefixItems" , ( prefixItemsNode , instanceNode ) => {
339
+ keywordHandlers . set ( "prefixItems" , ( prefixItemsNode , instanceNode , _schemaNode , errors ) => {
310
340
if ( instanceNode . jsonType !== "array" ) {
311
341
return true ;
312
342
}
@@ -315,15 +345,15 @@ keywordHandlers.set("prefixItems", (prefixItemsNode, instanceNode) => {
315
345
316
346
let isValid = true ;
317
347
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 ) ) {
319
349
isValid = false ;
320
350
}
321
351
}
322
352
323
353
return isValid ;
324
354
} ) ;
325
355
326
- keywordHandlers . set ( "properties" , ( propertiesNode , instanceNode ) => {
356
+ keywordHandlers . set ( "properties" , ( propertiesNode , instanceNode , _schemaNode , errors ) => {
327
357
if ( instanceNode . jsonType !== "object" ) {
328
358
return true ;
329
359
}
@@ -335,7 +365,7 @@ keywordHandlers.set("properties", (propertiesNode, instanceNode) => {
335
365
const [ propertyNameNode , instancePropertyNode ] = jsonPropertyNode . children ;
336
366
if ( jsonObjectHas ( propertyNameNode . value , propertiesNode ) ) {
337
367
const schemaPropertyNode = jsonPointerStep ( propertyNameNode . value , propertiesNode ) ;
338
- if ( ! validateSchema ( schemaPropertyNode , instancePropertyNode ) ) {
368
+ if ( ! validateSchema ( schemaPropertyNode , instancePropertyNode , errors ) ) {
339
369
isValid = false ;
340
370
}
341
371
}
@@ -344,7 +374,7 @@ keywordHandlers.set("properties", (propertiesNode, instanceNode) => {
344
374
return isValid ;
345
375
} ) ;
346
376
347
- keywordHandlers . set ( "propertyNames" , ( propertyNamesNode , instanceNode ) => {
377
+ keywordHandlers . set ( "propertyNames" , ( propertyNamesNode , instanceNode , _schemaNode , errors ) => {
348
378
if ( instanceNode . jsonType !== "object" ) {
349
379
return true ;
350
380
}
@@ -358,7 +388,7 @@ keywordHandlers.set("propertyNames", (propertyNamesNode, instanceNode) => {
358
388
value : propertyNode . children [ 0 ] . value ,
359
389
location : JsonPointer . append ( propertyNode . children [ 0 ] . value , instanceNode . location )
360
390
} ;
361
- if ( ! validateSchema ( propertyNamesNode , keyNode ) ) {
391
+ if ( ! validateSchema ( propertyNamesNode , keyNode , errors ) ) {
362
392
isValid = false ;
363
393
}
364
394
}
0 commit comments