@@ -13,7 +13,6 @@ const { randomUUID } = require('crypto')
13
13
const validate = require ( './schema-validator' )
14
14
15
15
let largeArraySize = 2e4
16
- let stringSimilarity = null
17
16
let largeArrayMechanism = 'default'
18
17
const validLargeArrayMechanisms = [
19
18
'default' ,
@@ -45,6 +44,7 @@ function isValidSchema (schema, name) {
45
44
function mergeLocation ( source , dest ) {
46
45
return {
47
46
schema : dest . schema || source . schema ,
47
+ schemaRef : dest . schemaRef || source . schemaRef ,
48
48
root : dest . root || source . root ,
49
49
externalSchema : dest . externalSchema || source . externalSchema
50
50
}
@@ -55,6 +55,7 @@ const objectReferenceSerializersMap = new Map()
55
55
const schemaReferenceMap = new Map ( )
56
56
57
57
let ajvInstance = null
58
+ let schemaRefResolver = null
58
59
59
60
class Serializer {
60
61
constructor ( options = { } ) {
@@ -223,6 +224,40 @@ class Serializer {
223
224
}
224
225
}
225
226
227
+ function getSchema ( ref , location ) {
228
+ let ajvSchema
229
+ let schemaRef
230
+
231
+ if ( ref [ 0 ] === '#' ) {
232
+ schemaRef = location . schemaRef + ref
233
+ } else {
234
+ schemaRef = ref
235
+ location . schemaRef = ref . split ( '#' ) [ 0 ]
236
+ }
237
+
238
+ try {
239
+ ajvSchema = schemaRefResolver . getSchema ( schemaRef )
240
+ } catch ( error ) {
241
+ throw new Error ( `Cannot find reference "${ ref } "` )
242
+ }
243
+
244
+ if ( ajvSchema === undefined ) {
245
+ throw new Error ( `Cannot find reference "${ ref } "` )
246
+ }
247
+
248
+ let schema = ajvSchema . schema
249
+ if ( schema . $ref !== undefined ) {
250
+ schema = getSchema ( schema . $ref , location ) . schema
251
+ }
252
+
253
+ return {
254
+ root : schema ,
255
+ schema,
256
+ schemaRef : location . schemaRef ,
257
+ externalSchema : location . externalSchema
258
+ }
259
+ }
260
+
226
261
function build ( schema , options ) {
227
262
arrayItemsReferenceSerializersMap . clear ( )
228
263
objectReferenceSerializersMap . clear ( )
@@ -256,11 +291,28 @@ function build (schema, options) {
256
291
}
257
292
} )
258
293
294
+ schemaRefResolver = new Ajv ( )
295
+ const mainSchemaRef = schema . $id || randomUUID ( )
296
+
259
297
isValidSchema ( schema )
298
+ schemaRefResolver . addSchema ( schema , mainSchemaRef )
260
299
if ( options . schema ) {
261
- // eslint-disable-next-line
262
- for ( var key of Object . keys ( options . schema ) ) {
263
- isValidSchema ( options . schema [ key ] , key )
300
+ for ( const key of Object . keys ( options . schema ) ) {
301
+ const externalSchema = options . schema [ key ]
302
+ isValidSchema ( externalSchema , key )
303
+
304
+ if ( externalSchema . $id !== undefined ) {
305
+ if ( externalSchema . $id [ 0 ] === '#' ) {
306
+ schemaRefResolver . addSchema ( externalSchema , key + externalSchema . $id )
307
+ } else {
308
+ schemaRefResolver . addSchema ( externalSchema )
309
+ if ( externalSchema . $id !== key ) {
310
+ schemaRefResolver . addSchema ( { $ref : externalSchema . $id } , key )
311
+ }
312
+ }
313
+ } else {
314
+ schemaRefResolver . addSchema ( externalSchema , key )
315
+ }
264
316
}
265
317
}
266
318
@@ -290,12 +342,13 @@ function build (schema, options) {
290
342
291
343
let location = {
292
344
schema,
345
+ schemaRef : mainSchemaRef ,
293
346
root : schema ,
294
347
externalSchema : options . schema
295
348
}
296
349
297
350
if ( schema . $ref ) {
298
- location = refFinder ( schema . $ref , location )
351
+ location = getSchema ( schema . $ref , location )
299
352
schema = location . schema
300
353
}
301
354
@@ -326,6 +379,7 @@ function build (schema, options) {
326
379
const stringifyFunc = contextFunc ( ajvInstance , serializer )
327
380
328
381
ajvInstance = null
382
+ schemaRefResolver = null
329
383
arrayItemsReferenceSerializersMap . clear ( )
330
384
objectReferenceSerializersMap . clear ( )
331
385
schemaReferenceMap . clear ( )
@@ -413,7 +467,7 @@ function addPatternProperties (location) {
413
467
Object . keys ( pp ) . forEach ( ( regex , index ) => {
414
468
let ppLocation = mergeLocation ( location , { schema : pp [ regex ] } )
415
469
if ( pp [ regex ] . $ref ) {
416
- ppLocation = refFinder ( pp [ regex ] . $ref , location )
470
+ ppLocation = getSchema ( pp [ regex ] . $ref , location )
417
471
pp [ regex ] = ppLocation . schema
418
472
}
419
473
@@ -461,7 +515,7 @@ function additionalProperty (location) {
461
515
}
462
516
let apLocation = mergeLocation ( location , { schema : ap } )
463
517
if ( ap . $ref ) {
464
- apLocation = refFinder ( ap . $ref , location )
518
+ apLocation = getSchema ( ap . $ref , location )
465
519
ap = apLocation . schema
466
520
}
467
521
@@ -490,140 +544,9 @@ function addAdditionalProperties (location) {
490
544
return { code, laterCode : additionalPropertyCode . laterCode }
491
545
}
492
546
493
- function idFinder ( schema , searchedId ) {
494
- let objSchema
495
- const explore = ( schema , searchedId ) => {
496
- Object . keys ( schema || { } ) . forEach ( ( key , i , a ) => {
497
- if ( key === '$id' && schema [ key ] === searchedId ) {
498
- objSchema = schema
499
- } else if ( objSchema === undefined && typeof schema [ key ] === 'object' ) {
500
- explore ( schema [ key ] , searchedId )
501
- }
502
- } )
503
- }
504
- explore ( schema , searchedId )
505
- return objSchema
506
- }
507
-
508
- function refFinder ( ref , location ) {
509
- const externalSchema = location . externalSchema
510
- let root = location . root
511
- let schema = location . schema
512
-
513
- if ( externalSchema && externalSchema [ ref ] ) {
514
- return {
515
- schema : externalSchema [ ref ] ,
516
- root : externalSchema [ ref ] ,
517
- externalSchema
518
- }
519
- }
520
-
521
- // Split file from walk
522
- ref = ref . split ( '#' )
523
-
524
- // Check schemaReferenceMap for $id entry
525
- if ( ref [ 0 ] && schemaReferenceMap . has ( ref [ 0 ] ) ) {
526
- schema = schemaReferenceMap . get ( ref [ 0 ] )
527
- root = schemaReferenceMap . get ( ref [ 0 ] )
528
- if ( schema . $ref ) {
529
- return refFinder ( schema . $ref , {
530
- schema,
531
- root,
532
- externalSchema
533
- } )
534
- }
535
- } else if ( ref [ 0 ] ) { // If external file
536
- schema = externalSchema [ ref [ 0 ] ]
537
- root = externalSchema [ ref [ 0 ] ]
538
-
539
- if ( schema === undefined ) {
540
- findBadKey ( externalSchema , [ ref [ 0 ] ] )
541
- }
542
-
543
- if ( schema . $ref ) {
544
- return refFinder ( schema . $ref , {
545
- schema,
546
- root,
547
- externalSchema
548
- } )
549
- }
550
- }
551
-
552
- let code = 'return schema'
553
- // If it has a path
554
- if ( ref [ 1 ] ) {
555
- // ref[1] could contain a JSON pointer - ex: /definitions/num
556
- // or plain name fragment id without suffix # - ex: customId
557
- const walk = ref [ 1 ] . split ( '/' )
558
- if ( walk . length === 1 ) {
559
- const targetId = `#${ ref [ 1 ] } `
560
- let dereferenced = idFinder ( schema , targetId )
561
- if ( dereferenced === undefined && ! ref [ 0 ] ) {
562
- // eslint-disable-next-line
563
- for ( var key of Object . keys ( externalSchema ) ) {
564
- dereferenced = idFinder ( externalSchema [ key ] , targetId )
565
- if ( dereferenced !== undefined ) {
566
- root = externalSchema [ key ]
567
- break
568
- }
569
- }
570
- }
571
-
572
- return {
573
- schema : dereferenced ,
574
- root,
575
- externalSchema
576
- }
577
- } else {
578
- // eslint-disable-next-line
579
- for ( var i = 1 ; i < walk . length ; i ++ ) {
580
- code += `[${ JSON . stringify ( walk [ i ] ) } ]`
581
- }
582
- }
583
- }
584
- let result
585
- try {
586
- result = ( new Function ( 'schema' , code ) ) ( root )
587
- } catch ( err ) { }
588
-
589
- if ( result === undefined && ref [ 1 ] ) {
590
- const walk = ref [ 1 ] . split ( '/' )
591
- findBadKey ( schema , walk . slice ( 1 ) )
592
- }
593
-
594
- if ( result . $ref ) {
595
- return refFinder ( result . $ref , {
596
- schema,
597
- root,
598
- externalSchema
599
- } )
600
- }
601
-
602
- return {
603
- schema : result ,
604
- root,
605
- externalSchema
606
- }
607
-
608
- function findBadKey ( obj , keys ) {
609
- if ( keys . length === 0 ) return null
610
- const key = keys . shift ( )
611
- if ( obj [ key ] === undefined ) {
612
- stringSimilarity = stringSimilarity || require ( 'string-similarity' )
613
- const { bestMatch } = stringSimilarity . findBestMatch ( key , Object . keys ( obj ) )
614
- if ( bestMatch . rating >= 0.5 ) {
615
- throw new Error ( `Cannot find reference ${ JSON . stringify ( key ) } , did you mean ${ JSON . stringify ( bestMatch . target ) } ?` )
616
- } else {
617
- throw new Error ( `Cannot find reference ${ JSON . stringify ( key ) } ` )
618
- }
619
- }
620
- return findBadKey ( obj [ key ] , keys )
621
- }
622
- }
623
-
624
547
function buildCode ( location , code , laterCode , locationPath ) {
625
548
if ( location . schema . $ref ) {
626
- location = refFinder ( location . schema . $ref , location )
549
+ location = getSchema ( location . schema . $ref , location )
627
550
}
628
551
629
552
const schema = location . schema
@@ -632,7 +555,7 @@ function buildCode (location, code, laterCode, locationPath) {
632
555
Object . keys ( schema . properties || { } ) . forEach ( ( key ) => {
633
556
let propertyLocation = mergeLocation ( location , { schema : schema . properties [ key ] } )
634
557
if ( schema . properties [ key ] . $ref ) {
635
- propertyLocation = refFinder ( schema . properties [ key ] . $ref , location )
558
+ propertyLocation = getSchema ( schema . properties [ key ] . $ref , location )
636
559
schema . properties [ key ] = propertyLocation . schema
637
560
}
638
561
@@ -682,7 +605,7 @@ function buildCode (location, code, laterCode, locationPath) {
682
605
function mergeAllOfSchema ( location , schema , mergedSchema ) {
683
606
for ( let allOfSchema of schema . allOf ) {
684
607
if ( allOfSchema . $ref ) {
685
- allOfSchema = refFinder ( allOfSchema . $ref , mergeLocation ( location , { schema : allOfSchema } ) ) . schema
608
+ allOfSchema = getSchema ( allOfSchema . $ref , mergeLocation ( location , { schema : allOfSchema } ) ) . schema
686
609
}
687
610
688
611
let allOfSchemaType = allOfSchema . type
@@ -934,7 +857,7 @@ function buildArray (location, code, functionName, locationPath) {
934
857
schema [ fjsCloned ] = true
935
858
}
936
859
937
- location = refFinder ( schema . items . $ref , location )
860
+ location = getSchema ( schema . items . $ref , location )
938
861
schema . items = location . schema
939
862
940
863
if ( arrayItemsReferenceSerializersMap . has ( schema . items ) ) {
@@ -1068,7 +991,7 @@ function dereferenceOfRefs (location, type) {
1068
991
// follow the refs
1069
992
let sLocation = mergeLocation ( location , { schema : s } )
1070
993
while ( s . $ref ) {
1071
- sLocation = refFinder ( s . $ref , sLocation )
994
+ sLocation = getSchema ( s . $ref , sLocation )
1072
995
schema [ type ] [ index ] = sLocation . schema
1073
996
s = schema [ type ] [ index ]
1074
997
}
@@ -1087,7 +1010,7 @@ function buildValue (laterCode, locationPath, input, location) {
1087
1010
let schema = location . schema
1088
1011
1089
1012
if ( schema . $ref ) {
1090
- schema = refFinder ( schema . $ref , location )
1013
+ schema = getSchema ( schema . $ref , location )
1091
1014
}
1092
1015
1093
1016
if ( schema . type === undefined ) {
0 commit comments