@@ -13,6 +13,7 @@ const { randomUUID } = require('crypto')
1313const validate = require ( './schema-validator' )
1414
1515let largeArraySize = 2e4
16+ let stringSimilarity = null
1617let largeArrayMechanism = 'default'
1718const validLargeArrayMechanisms = [
1819 'default' ,
@@ -44,7 +45,6 @@ function isValidSchema (schema, name) {
4445function mergeLocation ( source , dest ) {
4546 return {
4647 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,7 +55,6 @@ const objectReferenceSerializersMap = new Map()
5555const schemaReferenceMap = new Map ( )
5656
5757let ajvInstance = null
58- let schemaRefResolver = null
5958
6059class Serializer {
6160 constructor ( options = { } ) {
@@ -224,40 +223,6 @@ class Serializer {
224223 }
225224}
226225
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-
261226function build ( schema , options ) {
262227 arrayItemsReferenceSerializersMap . clear ( )
263228 objectReferenceSerializersMap . clear ( )
@@ -291,28 +256,11 @@ function build (schema, options) {
291256 }
292257 } )
293258
294- schemaRefResolver = new Ajv ( )
295- const mainSchemaRef = schema . $id || randomUUID ( )
296-
297259 isValidSchema ( schema )
298- schemaRefResolver . addSchema ( schema , mainSchemaRef )
299260 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 )
316264 }
317265 }
318266
@@ -342,13 +290,12 @@ function build (schema, options) {
342290
343291 let location = {
344292 schema,
345- schemaRef : mainSchemaRef ,
346293 root : schema ,
347294 externalSchema : options . schema
348295 }
349296
350297 if ( schema . $ref ) {
351- location = getSchema ( schema . $ref , location )
298+ location = refFinder ( schema . $ref , location )
352299 schema = location . schema
353300 }
354301
@@ -379,7 +326,6 @@ function build (schema, options) {
379326 const stringifyFunc = contextFunc ( ajvInstance , serializer )
380327
381328 ajvInstance = null
382- schemaRefResolver = null
383329 arrayItemsReferenceSerializersMap . clear ( )
384330 objectReferenceSerializersMap . clear ( )
385331 schemaReferenceMap . clear ( )
@@ -467,7 +413,7 @@ function addPatternProperties (location) {
467413 Object . keys ( pp ) . forEach ( ( regex , index ) => {
468414 let ppLocation = mergeLocation ( location , { schema : pp [ regex ] } )
469415 if ( pp [ regex ] . $ref ) {
470- ppLocation = getSchema ( pp [ regex ] . $ref , location )
416+ ppLocation = refFinder ( pp [ regex ] . $ref , location )
471417 pp [ regex ] = ppLocation . schema
472418 }
473419
@@ -515,7 +461,7 @@ function additionalProperty (location) {
515461 }
516462 let apLocation = mergeLocation ( location , { schema : ap } )
517463 if ( ap . $ref ) {
518- apLocation = getSchema ( ap . $ref , location )
464+ apLocation = refFinder ( ap . $ref , location )
519465 ap = apLocation . schema
520466 }
521467
@@ -544,9 +490,140 @@ function addAdditionalProperties (location) {
544490 return { code, laterCode : additionalPropertyCode . laterCode }
545491}
546492
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+
547624function buildCode ( location , code , laterCode , locationPath ) {
548625 if ( location . schema . $ref ) {
549- location = getSchema ( location . schema . $ref , location )
626+ location = refFinder ( location . schema . $ref , location )
550627 }
551628
552629 const schema = location . schema
@@ -555,7 +632,7 @@ function buildCode (location, code, laterCode, locationPath) {
555632 Object . keys ( schema . properties || { } ) . forEach ( ( key ) => {
556633 let propertyLocation = mergeLocation ( location , { schema : schema . properties [ key ] } )
557634 if ( schema . properties [ key ] . $ref ) {
558- propertyLocation = getSchema ( schema . properties [ key ] . $ref , location )
635+ propertyLocation = refFinder ( schema . properties [ key ] . $ref , location )
559636 schema . properties [ key ] = propertyLocation . schema
560637 }
561638
@@ -605,7 +682,7 @@ function buildCode (location, code, laterCode, locationPath) {
605682function mergeAllOfSchema ( location , schema , mergedSchema ) {
606683 for ( let allOfSchema of schema . allOf ) {
607684 if ( allOfSchema . $ref ) {
608- allOfSchema = getSchema ( allOfSchema . $ref , mergeLocation ( location , { schema : allOfSchema } ) ) . schema
685+ allOfSchema = refFinder ( allOfSchema . $ref , mergeLocation ( location , { schema : allOfSchema } ) ) . schema
609686 }
610687
611688 let allOfSchemaType = allOfSchema . type
@@ -857,7 +934,7 @@ function buildArray (location, code, functionName, locationPath) {
857934 schema [ fjsCloned ] = true
858935 }
859936
860- location = getSchema ( schema . items . $ref , location )
937+ location = refFinder ( schema . items . $ref , location )
861938 schema . items = location . schema
862939
863940 if ( arrayItemsReferenceSerializersMap . has ( schema . items ) ) {
@@ -991,7 +1068,7 @@ function dereferenceOfRefs (location, type) {
9911068 // follow the refs
9921069 let sLocation = mergeLocation ( location , { schema : s } )
9931070 while ( s . $ref ) {
994- sLocation = getSchema ( s . $ref , sLocation )
1071+ sLocation = refFinder ( s . $ref , sLocation )
9951072 schema [ type ] [ index ] = sLocation . schema
9961073 s = schema [ type ] [ index ]
9971074 }
@@ -1010,7 +1087,7 @@ function buildValue (laterCode, locationPath, input, location) {
10101087 let schema = location . schema
10111088
10121089 if ( schema . $ref ) {
1013- schema = getSchema ( schema . $ref , location )
1090+ schema = refFinder ( schema . $ref , location )
10141091 }
10151092
10161093 if ( schema . type === undefined ) {
0 commit comments