@@ -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