@@ -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> */
89118const 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
188218keywordHandlers . 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