@@ -13,7 +13,6 @@ const { randomUUID } = require('crypto')
1313const validate = require ( './schema-validator' )
1414
1515let largeArraySize = 2e4
16- let stringSimilarity = null
1716let largeArrayMechanism = 'default'
1817const validLargeArrayMechanisms = [
1918 'default' ,
@@ -45,6 +44,7 @@ function isValidSchema (schema, name) {
4544function mergeLocation ( source , dest ) {
4645 return {
4746 schema : dest . schema || source . schema ,
47+ schemaRef : dest . schemaRef || source . schemaRef ,
4848 root : dest . root || source . root ,
4949 externalSchema : dest . externalSchema || source . externalSchema
5050 }
@@ -55,6 +55,7 @@ const objectReferenceSerializersMap = new Map()
5555const schemaReferenceMap = new Map ( )
5656
5757let ajvInstance = null
58+ let schemaRefResolver = null
5859
5960class Serializer {
6061 constructor ( options = { } ) {
@@ -223,6 +224,40 @@ class Serializer {
223224 }
224225}
225226
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+
226261function build ( schema , options ) {
227262 arrayItemsReferenceSerializersMap . clear ( )
228263 objectReferenceSerializersMap . clear ( )
@@ -256,11 +291,28 @@ function build (schema, options) {
256291 }
257292 } )
258293
294+ schemaRefResolver = new Ajv ( )
295+ const mainSchemaRef = schema . $id || randomUUID ( )
296+
259297 isValidSchema ( schema )
298+ schemaRefResolver . addSchema ( schema , mainSchemaRef )
260299 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+ }
264316 }
265317 }
266318
@@ -290,12 +342,13 @@ function build (schema, options) {
290342
291343 let location = {
292344 schema,
345+ schemaRef : mainSchemaRef ,
293346 root : schema ,
294347 externalSchema : options . schema
295348 }
296349
297350 if ( schema . $ref ) {
298- location = refFinder ( schema . $ref , location )
351+ location = getSchema ( schema . $ref , location )
299352 schema = location . schema
300353 }
301354
@@ -326,6 +379,7 @@ function build (schema, options) {
326379 const stringifyFunc = contextFunc ( ajvInstance , serializer )
327380
328381 ajvInstance = null
382+ schemaRefResolver = null
329383 arrayItemsReferenceSerializersMap . clear ( )
330384 objectReferenceSerializersMap . clear ( )
331385 schemaReferenceMap . clear ( )
@@ -413,7 +467,7 @@ function addPatternProperties (location) {
413467 Object . keys ( pp ) . forEach ( ( regex , index ) => {
414468 let ppLocation = mergeLocation ( location , { schema : pp [ regex ] } )
415469 if ( pp [ regex ] . $ref ) {
416- ppLocation = refFinder ( pp [ regex ] . $ref , location )
470+ ppLocation = getSchema ( pp [ regex ] . $ref , location )
417471 pp [ regex ] = ppLocation . schema
418472 }
419473
@@ -461,7 +515,7 @@ function additionalProperty (location) {
461515 }
462516 let apLocation = mergeLocation ( location , { schema : ap } )
463517 if ( ap . $ref ) {
464- apLocation = refFinder ( ap . $ref , location )
518+ apLocation = getSchema ( ap . $ref , location )
465519 ap = apLocation . schema
466520 }
467521
@@ -490,140 +544,9 @@ function addAdditionalProperties (location) {
490544 return { code, laterCode : additionalPropertyCode . laterCode }
491545}
492546
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-
624547function buildCode ( location , code , laterCode , locationPath ) {
625548 if ( location . schema . $ref ) {
626- location = refFinder ( location . schema . $ref , location )
549+ location = getSchema ( location . schema . $ref , location )
627550 }
628551
629552 const schema = location . schema
@@ -632,7 +555,7 @@ function buildCode (location, code, laterCode, locationPath) {
632555 Object . keys ( schema . properties || { } ) . forEach ( ( key ) => {
633556 let propertyLocation = mergeLocation ( location , { schema : schema . properties [ key ] } )
634557 if ( schema . properties [ key ] . $ref ) {
635- propertyLocation = refFinder ( schema . properties [ key ] . $ref , location )
558+ propertyLocation = getSchema ( schema . properties [ key ] . $ref , location )
636559 schema . properties [ key ] = propertyLocation . schema
637560 }
638561
@@ -682,7 +605,7 @@ function buildCode (location, code, laterCode, locationPath) {
682605function mergeAllOfSchema ( location , schema , mergedSchema ) {
683606 for ( let allOfSchema of schema . allOf ) {
684607 if ( allOfSchema . $ref ) {
685- allOfSchema = refFinder ( allOfSchema . $ref , mergeLocation ( location , { schema : allOfSchema } ) ) . schema
608+ allOfSchema = getSchema ( allOfSchema . $ref , mergeLocation ( location , { schema : allOfSchema } ) ) . schema
686609 }
687610
688611 let allOfSchemaType = allOfSchema . type
@@ -934,7 +857,7 @@ function buildArray (location, code, functionName, locationPath) {
934857 schema [ fjsCloned ] = true
935858 }
936859
937- location = refFinder ( schema . items . $ref , location )
860+ location = getSchema ( schema . items . $ref , location )
938861 schema . items = location . schema
939862
940863 if ( arrayItemsReferenceSerializersMap . has ( schema . items ) ) {
@@ -1068,7 +991,7 @@ function dereferenceOfRefs (location, type) {
1068991 // follow the refs
1069992 let sLocation = mergeLocation ( location , { schema : s } )
1070993 while ( s . $ref ) {
1071- sLocation = refFinder ( s . $ref , sLocation )
994+ sLocation = getSchema ( s . $ref , sLocation )
1072995 schema [ type ] [ index ] = sLocation . schema
1073996 s = schema [ type ] [ index ]
1074997 }
@@ -1087,7 +1010,7 @@ function buildValue (laterCode, locationPath, input, location) {
10871010 let schema = location . schema
10881011
10891012 if ( schema . $ref ) {
1090- schema = refFinder ( schema . $ref , location )
1013+ schema = getSchema ( schema . $ref , location )
10911014 }
10921015
10931016 if ( schema . type === undefined ) {
0 commit comments