Skip to content

Commit 01645d2

Browse files
committed
Revert "fix: use ajv for schema ref resolving (#454)"
This reverts commit b41b800.
1 parent b41b800 commit 01645d2

File tree

3 files changed

+188
-292
lines changed

3 files changed

+188
-292
lines changed

index.js

Lines changed: 144 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const { randomUUID } = require('crypto')
1313
const validate = require('./schema-validator')
1414

1515
let largeArraySize = 2e4
16+
let stringSimilarity = null
1617
let largeArrayMechanism = 'default'
1718
const validLargeArrayMechanisms = [
1819
'default',
@@ -44,7 +45,6 @@ function isValidSchema (schema, name) {
4445
function 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()
5555
const schemaReferenceMap = new Map()
5656

5757
let ajvInstance = null
58-
let schemaRefResolver = null
5958

6059
class 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-
261226
function 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+
547624
function 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) {
605682
function 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) {

test/allof.test.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -402,14 +402,12 @@ test('object with external $refs in allOf', (t) => {
402402
}
403403
},
404404
second: {
405-
definitions: {
406-
id2: {
407-
$id: '#id2',
408-
type: 'object',
409-
properties: {
410-
id2: {
411-
type: 'integer'
412-
}
405+
id2: {
406+
$id: '#id2',
407+
type: 'object',
408+
properties: {
409+
id2: {
410+
type: 'integer'
413411
}
414412
}
415413
}
@@ -424,7 +422,7 @@ test('object with external $refs in allOf', (t) => {
424422
$ref: 'first#/definitions/id1'
425423
},
426424
{
427-
$ref: 'second#/definitions/id2'
425+
$ref: 'second#id2'
428426
}
429427
]
430428
}

0 commit comments

Comments
 (0)