@@ -13,6 +13,7 @@ const { randomUUID } = require('crypto')
13
13
const validate = require ( './schema-validator' )
14
14
15
15
let largeArraySize = 2e4
16
+ let stringSimilarity = null
16
17
let largeArrayMechanism = 'default'
17
18
const validLargeArrayMechanisms = [
18
19
'default' ,
@@ -44,7 +45,6 @@ function isValidSchema (schema, name) {
44
45
function mergeLocation ( source , dest ) {
45
46
return {
46
47
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,7 +55,6 @@ const objectReferenceSerializersMap = new Map()
55
55
const schemaReferenceMap = new Map ( )
56
56
57
57
let ajvInstance = null
58
- let schemaRefResolver = null
59
58
60
59
class Serializer {
61
60
constructor ( options = { } ) {
@@ -224,40 +223,6 @@ class Serializer {
224
223
}
225
224
}
226
225
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
-
261
226
function build ( schema , options ) {
262
227
arrayItemsReferenceSerializersMap . clear ( )
263
228
objectReferenceSerializersMap . clear ( )
@@ -291,28 +256,11 @@ function build (schema, options) {
291
256
}
292
257
} )
293
258
294
- schemaRefResolver = new Ajv ( )
295
- const mainSchemaRef = schema . $id || randomUUID ( )
296
-
297
259
isValidSchema ( schema )
298
- schemaRefResolver . addSchema ( schema , mainSchemaRef )
299
260
if ( options . schema ) {
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
- }
261
+ // eslint-disable-next-line
262
+ for ( var key of Object . keys ( options . schema ) ) {
263
+ isValidSchema ( options . schema [ key ] , key )
316
264
}
317
265
}
318
266
@@ -342,13 +290,12 @@ function build (schema, options) {
342
290
343
291
let location = {
344
292
schema,
345
- schemaRef : mainSchemaRef ,
346
293
root : schema ,
347
294
externalSchema : options . schema
348
295
}
349
296
350
297
if ( schema . $ref ) {
351
- location = getSchema ( schema . $ref , location )
298
+ location = refFinder ( schema . $ref , location )
352
299
schema = location . schema
353
300
}
354
301
@@ -379,7 +326,6 @@ function build (schema, options) {
379
326
const stringifyFunc = contextFunc ( ajvInstance , serializer )
380
327
381
328
ajvInstance = null
382
- schemaRefResolver = null
383
329
arrayItemsReferenceSerializersMap . clear ( )
384
330
objectReferenceSerializersMap . clear ( )
385
331
schemaReferenceMap . clear ( )
@@ -467,7 +413,7 @@ function addPatternProperties (location) {
467
413
Object . keys ( pp ) . forEach ( ( regex , index ) => {
468
414
let ppLocation = mergeLocation ( location , { schema : pp [ regex ] } )
469
415
if ( pp [ regex ] . $ref ) {
470
- ppLocation = getSchema ( pp [ regex ] . $ref , location )
416
+ ppLocation = refFinder ( pp [ regex ] . $ref , location )
471
417
pp [ regex ] = ppLocation . schema
472
418
}
473
419
@@ -515,7 +461,7 @@ function additionalProperty (location) {
515
461
}
516
462
let apLocation = mergeLocation ( location , { schema : ap } )
517
463
if ( ap . $ref ) {
518
- apLocation = getSchema ( ap . $ref , location )
464
+ apLocation = refFinder ( ap . $ref , location )
519
465
ap = apLocation . schema
520
466
}
521
467
@@ -544,9 +490,140 @@ function addAdditionalProperties (location) {
544
490
return { code, laterCode : additionalPropertyCode . laterCode }
545
491
}
546
492
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
+
547
624
function buildCode ( location , code , laterCode , locationPath ) {
548
625
if ( location . schema . $ref ) {
549
- location = getSchema ( location . schema . $ref , location )
626
+ location = refFinder ( location . schema . $ref , location )
550
627
}
551
628
552
629
const schema = location . schema
@@ -555,7 +632,7 @@ function buildCode (location, code, laterCode, locationPath) {
555
632
Object . keys ( schema . properties || { } ) . forEach ( ( key ) => {
556
633
let propertyLocation = mergeLocation ( location , { schema : schema . properties [ key ] } )
557
634
if ( schema . properties [ key ] . $ref ) {
558
- propertyLocation = getSchema ( schema . properties [ key ] . $ref , location )
635
+ propertyLocation = refFinder ( schema . properties [ key ] . $ref , location )
559
636
schema . properties [ key ] = propertyLocation . schema
560
637
}
561
638
@@ -605,7 +682,7 @@ function buildCode (location, code, laterCode, locationPath) {
605
682
function mergeAllOfSchema ( location , schema , mergedSchema ) {
606
683
for ( let allOfSchema of schema . allOf ) {
607
684
if ( allOfSchema . $ref ) {
608
- allOfSchema = getSchema ( allOfSchema . $ref , mergeLocation ( location , { schema : allOfSchema } ) ) . schema
685
+ allOfSchema = refFinder ( allOfSchema . $ref , mergeLocation ( location , { schema : allOfSchema } ) ) . schema
609
686
}
610
687
611
688
let allOfSchemaType = allOfSchema . type
@@ -857,7 +934,7 @@ function buildArray (location, code, functionName, locationPath) {
857
934
schema [ fjsCloned ] = true
858
935
}
859
936
860
- location = getSchema ( schema . items . $ref , location )
937
+ location = refFinder ( schema . items . $ref , location )
861
938
schema . items = location . schema
862
939
863
940
if ( arrayItemsReferenceSerializersMap . has ( schema . items ) ) {
@@ -991,7 +1068,7 @@ function dereferenceOfRefs (location, type) {
991
1068
// follow the refs
992
1069
let sLocation = mergeLocation ( location , { schema : s } )
993
1070
while ( s . $ref ) {
994
- sLocation = getSchema ( s . $ref , sLocation )
1071
+ sLocation = refFinder ( s . $ref , sLocation )
995
1072
schema [ type ] [ index ] = sLocation . schema
996
1073
s = schema [ type ] [ index ]
997
1074
}
@@ -1010,7 +1087,7 @@ function buildValue (laterCode, locationPath, input, location) {
1010
1087
let schema = location . schema
1011
1088
1012
1089
if ( schema . $ref ) {
1013
- schema = getSchema ( schema . $ref , location )
1090
+ schema = refFinder ( schema . $ref , location )
1014
1091
}
1015
1092
1016
1093
if ( schema . type === undefined ) {
0 commit comments