Skip to content

Commit 1530ee3

Browse files
fix: merged schemas reference resolving
1 parent 13445f7 commit 1530ee3

File tree

2 files changed

+127
-36
lines changed

2 files changed

+127
-36
lines changed

index.js

Lines changed: 82 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,47 @@ function isValidSchema (schema, name) {
4040
}
4141
}
4242

43-
function mergeLocation (location, key) {
44-
return {
45-
schema: location.schema[key],
46-
schemaId: location.schemaId,
47-
jsonPointer: location.jsonPointer + '/' + key,
48-
isValidated: location.isValidated
43+
class Location {
44+
constructor (schema, schemaId, jsonPointer = '#', isValidated = false) {
45+
this.schema = schema
46+
this.schemaId = schemaId
47+
this.jsonPointer = jsonPointer
48+
this.isValidated = isValidated
49+
50+
this.isMerged = false
51+
this.mergedSchemaId = null
52+
this.mergedJsonPointer = null
53+
}
54+
55+
getPropertyLocation(propertyName) {
56+
const propertyLocation = new Location(
57+
this.schema[propertyName],
58+
this.schemaId,
59+
this.jsonPointer + '/' + propertyName,
60+
this.isValidated
61+
)
62+
63+
if (this.isMerged) {
64+
propertyLocation.addMergedSchema(
65+
this.mergedSchemaId,
66+
this.mergedJsonPointer + '/' + propertyName
67+
)
68+
}
69+
70+
return propertyLocation
71+
}
72+
73+
getSchemaRef () {
74+
if (this.isMerged) {
75+
return this.mergedSchemaId + this.mergedJsonPointer
76+
}
77+
return this.schemaId + this.jsonPointer
78+
}
79+
80+
addMergedSchema (schemaId, jsonPointer = '#') {
81+
this.isMerged = true
82+
this.mergedSchemaId = schemaId
83+
this.mergedJsonPointer = jsonPointer
4984
}
5085
}
5186

@@ -68,11 +103,12 @@ function resolveRef (location, ref) {
68103
validatorSchemasIds.add(schemaId)
69104
}
70105

106+
const newLocation = new Location(schema, schemaId, jsonPointer, location.isValidated)
71107
if (schema.$ref !== undefined) {
72-
return resolveRef({ schema, schemaId, jsonPointer }, schema.$ref)
108+
return resolveRef(newLocation, schema.$ref)
73109
}
74110

75-
return { schema, schemaId, jsonPointer, isValidated: location.isValidated }
111+
return newLocation
76112
}
77113

78114
const contextFunctionsNamesBySchema = new Map()
@@ -125,7 +161,7 @@ function build (schema, options) {
125161
}
126162
}
127163

128-
const location = { schema, schemaId: rootSchemaId, jsonPointer: '#', isValidated: false }
164+
const location = new Location(schema, rootSchemaId)
129165
const code = buildValue(location, 'input')
130166

131167
const contextFunctionCode = `
@@ -248,9 +284,9 @@ function addPatternProperties (location) {
248284
if (properties[keys[i]]) continue
249285
`
250286

251-
const patternPropertiesLocation = mergeLocation(location, 'patternProperties')
287+
const patternPropertiesLocation = location.getPropertyLocation('patternProperties')
252288
Object.keys(pp).forEach((regex) => {
253-
let ppLocation = mergeLocation(patternPropertiesLocation, regex)
289+
let ppLocation = patternPropertiesLocation.getPropertyLocation(regex)
254290
if (pp[regex].$ref) {
255291
ppLocation = resolveRef(ppLocation, pp[regex].$ref)
256292
pp[regex] = ppLocation.schema
@@ -296,7 +332,7 @@ function additionalProperty (location) {
296332
return code
297333
}
298334

299-
let apLocation = mergeLocation(location, 'additionalProperties')
335+
let apLocation = location.getPropertyLocation('additionalProperties')
300336
if (apLocation.schema.$ref) {
301337
apLocation = resolveRef(apLocation, apLocation.schema.$ref)
302338
}
@@ -333,9 +369,12 @@ function buildCode (location) {
333369

334370
let code = ''
335371

336-
const propertiesLocation = mergeLocation(location, 'properties')
372+
if (!(location instanceof Location)) {
373+
console.log(location)
374+
}
375+
const propertiesLocation = location.getPropertyLocation('properties')
337376
Object.keys(schema.properties || {}).forEach((key) => {
338-
let propertyLocation = mergeLocation(propertiesLocation, key)
377+
let propertyLocation = propertiesLocation.getPropertyLocation(key)
339378
if (propertyLocation.schema.$ref) {
340379
propertyLocation = resolveRef(location, propertyLocation.schema.$ref)
341380
}
@@ -382,13 +421,13 @@ function buildCode (location) {
382421
}
383422

384423
function mergeAllOfSchema (location, schema, mergedSchema) {
385-
const allOfLocation = mergeLocation(location, 'allOf')
424+
const allOfLocation = location.getPropertyLocation('allOf')
386425

387426
for (let i = 0; i < schema.allOf.length; i++) {
388427
let allOfSchema = schema.allOf[i]
389428

390429
if (allOfSchema.$ref) {
391-
const allOfSchemaLocation = mergeLocation(allOfLocation, i)
430+
const allOfSchemaLocation = allOfLocation.getPropertyLocation(i)
392431
allOfSchema = resolveRef(allOfSchemaLocation, allOfSchema.$ref).schema
393432
}
394433

@@ -477,8 +516,9 @@ function mergeAllOfSchema (location, schema, mergedSchema) {
477516

478517
mergedSchema.$id = `merged_${randomUUID()}`
479518
refResolver.addSchema(mergedSchema)
480-
location.schemaId = mergedSchema.$id
481-
location.jsonPointer = '#'
519+
520+
location.schema = mergedSchema
521+
location.addMergedSchema(mergedSchema.$id)
482522
}
483523

484524
function buildInnerObject (location) {
@@ -494,7 +534,11 @@ function buildInnerObject (location) {
494534

495535
function addIfThenElse (location, input) {
496536
location.isValidated = true
497-
validatorSchemasIds.add(location.schemaId)
537+
if (location.isMerged) {
538+
validatorSchemasIds.add(location.mergedSchemaId)
539+
} else {
540+
validatorSchemasIds.add(location.schemaId)
541+
}
498542

499543
const schema = merge({}, location.schema)
500544
const thenSchema = schema.then
@@ -504,13 +548,13 @@ function addIfThenElse (location, input) {
504548
delete schema.then
505549
delete schema.else
506550

507-
const ifLocation = mergeLocation(location, 'if')
508-
const ifSchemaRef = ifLocation.schemaId + ifLocation.jsonPointer
551+
const ifLocation = location.getPropertyLocation('if')
552+
const ifSchemaRef = ifLocation.getSchemaRef()
509553

510-
const thenLocation = mergeLocation(location, 'then')
554+
const thenLocation = location.getPropertyLocation('then')
511555
thenLocation.schema = merge(schema, thenSchema)
512556

513-
const elseLocation = mergeLocation(location, 'else')
557+
const elseLocation = location.getPropertyLocation('else')
514558
elseLocation.schema = merge(schema, elseSchema)
515559

516560
return `
@@ -565,7 +609,7 @@ function buildObject (location) {
565609
function buildArray (location) {
566610
const schema = location.schema
567611

568-
let itemsLocation = mergeLocation(location, 'items')
612+
let itemsLocation = location.getPropertyLocation('items')
569613
itemsLocation.schema = itemsLocation.schema || {}
570614

571615
if (itemsLocation.schema.$ref) {
@@ -617,7 +661,7 @@ function buildArray (location) {
617661
if (Array.isArray(itemsSchema)) {
618662
for (let i = 0; i < itemsSchema.length; i++) {
619663
const item = itemsSchema[i]
620-
const tmpRes = buildValue(mergeLocation(itemsLocation, i), `obj[${i}]`)
664+
const tmpRes = buildValue(itemsLocation.getPropertyLocation(i), `obj[${i}]`)
621665
functionCode += `
622666
if (${i} < arrayLength) {
623667
if (${buildArrayTypeCondition(item.type, `[${i}]`)}) {
@@ -713,11 +757,11 @@ function buildMultiTypeSerializer (location, input) {
713757

714758
let code = ''
715759

716-
const locationClone = clone(location)
717760
types.forEach((type, index) => {
761+
location.schema = { ...location.schema, type }
762+
const nestedResult = buildSingleTypeSerializer(location, input)
763+
718764
const statement = index === 0 ? 'if' : 'else if'
719-
locationClone.schema.type = type
720-
const nestedResult = buildSingleTypeSerializer(locationClone, input)
721765
switch (type) {
722766
case 'null':
723767
code += `
@@ -862,10 +906,8 @@ function buildValue (location, input) {
862906
}
863907

864908
if (schema.allOf) {
865-
const mergedSchema = clone(schema)
866-
mergeAllOfSchema(location, schema, mergedSchema)
867-
schema = mergedSchema
868-
location.schema = mergedSchema
909+
mergeAllOfSchema(location, schema, clone(schema))
910+
schema = location.schema
869911
}
870912

871913
const type = schema.type
@@ -874,14 +916,18 @@ function buildValue (location, input) {
874916

875917
if (type === undefined && (schema.anyOf || schema.oneOf)) {
876918
location.isValidated = true
877-
validatorSchemasIds.add(location.schemaId)
919+
if (location.isMerged) {
920+
validatorSchemasIds.add(location.mergedSchemaId)
921+
} else {
922+
validatorSchemasIds.add(location.schemaId)
923+
}
878924

879925
const type = schema.anyOf ? 'anyOf' : 'oneOf'
880-
const anyOfLocation = mergeLocation(location, type)
926+
const anyOfLocation = location.getPropertyLocation(type)
881927

882928
for (let index = 0; index < location.schema[type].length; index++) {
883-
const optionLocation = mergeLocation(anyOfLocation, index)
884-
const schemaRef = optionLocation.schemaId + optionLocation.jsonPointer
929+
const optionLocation = anyOfLocation.getPropertyLocation(index)
930+
const schemaRef = optionLocation.getSchemaRef()
885931
const nestedResult = buildValue(optionLocation, input)
886932
code += `
887933
${index === 0 ? 'if' : 'else if'}(validator.validate("${schemaRef}", ${input}))

test/allof.test.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,3 +437,48 @@ test('object with external $refs in allOf', (t) => {
437437
})
438438
t.equal(value, '{"id1":1,"id2":2}')
439439
})
440+
441+
test('allof with local anchor reference', (t) => {
442+
t.plan(1)
443+
444+
const externalSchemas = {
445+
Test: {
446+
$id: 'Test',
447+
definitions: {
448+
Problem: {
449+
type: 'object',
450+
properties: {
451+
type: {
452+
type: "string"
453+
}
454+
}
455+
},
456+
ValidationFragment: {
457+
type: 'string'
458+
},
459+
ValidationErrorProblem: {
460+
type: 'object',
461+
allOf: [
462+
{
463+
$ref: '#/definitions/Problem'
464+
},
465+
{
466+
type: 'object',
467+
properties: {
468+
validation: {
469+
$ref: '#/definitions/ValidationFragment'
470+
}
471+
}
472+
}
473+
]
474+
}
475+
}
476+
}
477+
}
478+
479+
const schema = { $ref: 'Test#/definitions/ValidationErrorProblem' }
480+
const stringify = build(schema, { schema: externalSchemas })
481+
const data = { type: 'foo', validation: 'bar' }
482+
483+
t.equal(stringify(data), JSON.stringify(data))
484+
})

0 commit comments

Comments
 (0)