Skip to content

Commit 0d36211

Browse files
fix: update merged schema local refs
1 parent 4f6f7b8 commit 0d36211

File tree

3 files changed

+167
-114
lines changed

3 files changed

+167
-114
lines changed

index.js

Lines changed: 114 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function resolveRef (context, location, ref) {
5252
hashIndex = ref.length
5353
}
5454

55-
const schemaId = ref.slice(0, hashIndex) || location.getOriginSchemaId()
55+
const schemaId = ref.slice(0, hashIndex) || location.schemaId
5656
const jsonPointer = ref.slice(hashIndex) || '#'
5757

5858
const schema = context.refResolver.getSchema(schemaId, jsonPointer)
@@ -407,107 +407,137 @@ function buildInnerObject (context, location) {
407407
return code
408408
}
409409

410-
function mergeAllOfSchema (context, location, schema, mergedSchema) {
411-
const allOfLocation = location.getPropertyLocation('allOf')
410+
function mergeAllOfSchema (context, location) {
411+
const schema = location.schema
412+
const schemaWithoutAllOf = clone(schema)
413+
const clonedAllOfSchemas = schemaWithoutAllOf.allOf
412414

413-
for (let i = 0; i < schema.allOf.length; i++) {
414-
let allOfSchema = schema.allOf[i]
415+
delete schemaWithoutAllOf.allOf
415416

416-
if (allOfSchema.$ref) {
417-
const allOfSchemaLocation = allOfLocation.getPropertyLocation(i)
418-
allOfSchema = resolveRef(context, allOfSchemaLocation, allOfSchema.$ref).schema
419-
}
417+
const allOfSchemasLocation = location.getPropertyLocation('allOf')
418+
allOfSchemasLocation.schema = clonedAllOfSchemas
420419

421-
let allOfSchemaType = allOfSchema.type
422-
if (allOfSchemaType === undefined) {
423-
allOfSchemaType = inferTypeByKeyword(allOfSchema)
424-
}
420+
const locations = [
421+
new Location(
422+
schemaWithoutAllOf,
423+
location.schemaId,
424+
location.jsonPointer
425+
)
426+
]
425427

426-
if (allOfSchemaType !== undefined) {
427-
if (
428-
mergedSchema.type !== undefined &&
429-
mergedSchema.type !== allOfSchemaType
430-
) {
431-
throw new Error('allOf schemas have different type values')
432-
}
433-
mergedSchema.type = allOfSchemaType
434-
}
428+
for (let i = 0; i < clonedAllOfSchemas.length; i++) {
429+
const allOfSchemaLocation = allOfSchemasLocation.getPropertyLocation(i)
430+
locations.push(allOfSchemaLocation)
431+
}
435432

436-
if (allOfSchema.format !== undefined) {
437-
if (
438-
mergedSchema.format !== undefined &&
439-
mergedSchema.format !== allOfSchema.format
440-
) {
441-
throw new Error('allOf schemas have different format values')
442-
}
443-
mergedSchema.format = allOfSchema.format
444-
}
433+
const mergedLocation = mergeSchemas(context, locations)
434+
return mergedLocation
435+
}
445436

446-
if (allOfSchema.nullable !== undefined) {
447-
if (
448-
mergedSchema.nullable !== undefined &&
449-
mergedSchema.nullable !== allOfSchema.nullable
450-
) {
451-
throw new Error('allOf schemas have different nullable values')
452-
}
453-
mergedSchema.nullable = allOfSchema.nullable
454-
}
437+
function mergeSchemas (context, locations) {
438+
const mergedSchema = {}
455439

456-
if (allOfSchema.properties !== undefined) {
457-
if (mergedSchema.properties === undefined) {
458-
mergedSchema.properties = {}
459-
}
460-
Object.assign(mergedSchema.properties, allOfSchema.properties)
461-
}
440+
for (let location of locations) {
441+
let schema = location.schema
442+
resolveRelativeRefs(schema, location.schemaId)
462443

463-
if (allOfSchema.additionalProperties !== undefined) {
464-
if (mergedSchema.additionalProperties === undefined) {
465-
mergedSchema.additionalProperties = {}
466-
}
467-
Object.assign(mergedSchema.additionalProperties, allOfSchema.additionalProperties)
468-
}
444+
if (schema.$ref) {
445+
const { $ref, ...schemaWithoutRef } = schema
446+
const refSchemaLocation = resolveRef(context, location, $ref)
447+
refSchemaLocation.schema = clone(refSchemaLocation.schema)
469448

470-
if (allOfSchema.patternProperties !== undefined) {
471-
if (mergedSchema.patternProperties === undefined) {
472-
mergedSchema.patternProperties = {}
473-
}
474-
Object.assign(mergedSchema.patternProperties, allOfSchema.patternProperties)
475-
}
449+
const newSchemaLocation = new Location(
450+
schemaWithoutRef,
451+
location.schemaId,
452+
location.jsonPointer
453+
)
476454

477-
if (allOfSchema.required !== undefined) {
478-
if (mergedSchema.required === undefined) {
479-
mergedSchema.required = []
480-
}
481-
mergedSchema.required.push(...allOfSchema.required)
455+
location = mergeSchemas(
456+
context,
457+
[refSchemaLocation, newSchemaLocation]
458+
)
459+
schema = location.schema
482460
}
483461

484-
if (allOfSchema.oneOf !== undefined) {
485-
if (mergedSchema.oneOf === undefined) {
486-
mergedSchema.oneOf = []
487-
}
488-
mergedSchema.oneOf.push(...allOfSchema.oneOf)
489-
}
462+
for (const key in schema) {
463+
const value = schema[key]
490464

491-
if (allOfSchema.anyOf !== undefined) {
492-
if (mergedSchema.anyOf === undefined) {
493-
mergedSchema.anyOf = []
465+
if (key === '$id') continue
466+
if (key === 'allOf') {
467+
if (mergedSchema.allOf === undefined) {
468+
mergedSchema.allOf = []
469+
}
470+
mergedSchema.allOf.push(...value)
471+
} else if (key === 'anyOf') {
472+
if (mergedSchema.anyOf === undefined) {
473+
mergedSchema.anyOf = []
474+
}
475+
mergedSchema.anyOf.push(...value)
476+
} else if (key === 'oneOf') {
477+
if (mergedSchema.oneOf === undefined) {
478+
mergedSchema.oneOf = []
479+
}
480+
mergedSchema.oneOf.push(...value)
481+
} else if (key === 'required') {
482+
if (mergedSchema.required === undefined) {
483+
mergedSchema.required = []
484+
}
485+
mergedSchema.required.push(...value)
486+
} else if (key === 'properties') {
487+
if (mergedSchema.properties === undefined) {
488+
mergedSchema.properties = {}
489+
}
490+
Object.assign(mergedSchema.properties, value)
491+
} else if (key === 'patternProperties') {
492+
if (mergedSchema.patternProperties === undefined) {
493+
mergedSchema.patternProperties = {}
494+
}
495+
Object.assign(mergedSchema.patternProperties, value)
496+
} else if (key === 'additionalProperties') {
497+
if (mergedSchema.additionalProperties === undefined) {
498+
mergedSchema.additionalProperties = {}
499+
}
500+
Object.assign(mergedSchema.additionalProperties, value)
501+
} else if (key === 'type') {
502+
if (mergedSchema.type !== undefined && mergedSchema.type !== value) {
503+
throw new Error('allOf schemas have different type values')
504+
}
505+
mergedSchema.type = value
506+
} else if (key === 'format') {
507+
if (mergedSchema.format !== undefined && mergedSchema.format !== value) {
508+
throw new Error('allOf schemas have different format values')
509+
}
510+
mergedSchema.format = value
511+
} else if (key === 'nullable') {
512+
if (mergedSchema.nullable !== undefined && mergedSchema.nullable !== value) {
513+
throw new Error('allOf schemas have different nullable values')
514+
}
515+
mergedSchema.nullable = value
494516
}
495-
mergedSchema.anyOf.push(...allOfSchema.anyOf)
496517
}
518+
}
519+
const mergedSchemaId = `__fjs_merged_${schemaIdCounter++}`
520+
const mergedSchemaLocation = new Location(mergedSchema, mergedSchemaId)
521+
context.refResolver.addSchema(mergedSchema, mergedSchemaId)
522+
return mergedSchemaLocation
523+
}
497524

498-
if (allOfSchema.allOf !== undefined) {
499-
mergeAllOfSchema(context, location, allOfSchema, mergedSchema)
525+
function resolveRelativeRefs (schema, schemaId) {
526+
if (schema.$ref?.charAt(0) === '#') {
527+
schema.$ref = schemaId + schema.$ref
528+
}
529+
for (const subSchema of Object.values(schema)) {
530+
if (
531+
typeof subSchema === 'object' &&
532+
subSchema.$id === undefined
533+
) {
534+
resolveRelativeRefs(subSchema, schemaId)
500535
}
501536
}
502-
delete mergedSchema.allOf
503-
504-
mergedSchema.$id = `__fjs_merged_${schemaIdCounter++}`
505-
context.refResolver.addSchema(mergedSchema)
506-
location.addMergedSchema(mergedSchema, mergedSchema.$id)
507537
}
508538

509539
function addIfThenElse (context, location, input) {
510-
context.validatorSchemasIds.add(location.getSchemaId())
540+
context.validatorSchemasIds.add(location.schemaId)
511541

512542
const schema = merge({}, location.schema)
513543
const thenSchema = schema.then
@@ -872,16 +902,16 @@ function buildValue (context, location, input) {
872902
}
873903

874904
if (schema.allOf) {
875-
mergeAllOfSchema(context, location, schema, clone(schema))
876-
schema = location.schema
905+
const mergedSchemaLocation = mergeAllOfSchema(context, location)
906+
return buildValue(context, mergedSchemaLocation, input)
877907
}
878908

879909
const type = schema.type
880910

881911
let code = ''
882912

883913
if ((type === undefined || type === 'object') && (schema.anyOf || schema.oneOf)) {
884-
context.validatorSchemasIds.add(location.getSchemaId())
914+
context.validatorSchemasIds.add(location.schemaId)
885915

886916
if (schema.type === 'object') {
887917
context.wrapObjects = false

lib/location.js

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ class Location {
55
this.schema = schema
66
this.schemaId = schemaId
77
this.jsonPointer = jsonPointer
8-
this.mergedSchemaId = null
98
}
109

1110
getPropertyLocation (propertyName) {
@@ -14,39 +13,11 @@ class Location {
1413
this.schemaId,
1514
this.jsonPointer + '/' + propertyName
1615
)
17-
18-
if (this.mergedSchemaId !== null) {
19-
propertyLocation.addMergedSchema(
20-
this.schema[propertyName],
21-
this.mergedSchemaId,
22-
this.jsonPointer + '/' + propertyName
23-
)
24-
}
25-
2616
return propertyLocation
2717
}
2818

29-
// Use this method to get current schema location.
30-
// Use it when you need to create reference to the current location.
31-
getSchemaId () {
32-
return this.mergedSchemaId || this.schemaId
33-
}
34-
35-
// Use this method to get original schema id for resolving user schema $refs
36-
// Don't join it with a JSON pointer to get the current location.
37-
getOriginSchemaId () {
38-
return this.schemaId
39-
}
40-
4119
getSchemaRef () {
42-
const schemaId = this.getSchemaId()
43-
return schemaId + this.jsonPointer
44-
}
45-
46-
addMergedSchema (mergedSchema, schemaId, jsonPointer = '#') {
47-
this.schema = mergedSchema
48-
this.mergedSchemaId = schemaId
49-
this.jsonPointer = jsonPointer
20+
return this.schemaId + this.jsonPointer
5021
}
5122
}
5223

test/allof.test.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,58 @@ test('allof with local anchor reference', (t) => {
553553
t.equal(stringify(data), JSON.stringify(data))
554554
})
555555

556+
test('allOf: multiple nested $ref properties', (t) => {
557+
t.plan(2)
558+
559+
const externalSchema1 = {
560+
$id: 'externalSchema1',
561+
oneOf: [
562+
{ $ref: '#/definitions/id1' }
563+
],
564+
definitions: {
565+
id1: {
566+
type: 'object',
567+
properties: {
568+
id1: {
569+
type: 'integer'
570+
}
571+
},
572+
additionalProperties: false
573+
}
574+
}
575+
}
576+
577+
const externalSchema2 = {
578+
$id: 'externalSchema2',
579+
oneOf: [
580+
{ $ref: '#/definitions/id2' }
581+
],
582+
definitions: {
583+
id2: {
584+
type: 'object',
585+
properties: {
586+
id2: {
587+
type: 'integer'
588+
}
589+
},
590+
additionalProperties: false
591+
}
592+
}
593+
}
594+
595+
const schema = {
596+
allOf: [
597+
{ $ref: 'externalSchema1' },
598+
{ $ref: 'externalSchema2' }
599+
]
600+
}
601+
602+
const stringify = build(schema, { schema: [externalSchema1, externalSchema2] })
603+
604+
t.equal(stringify({ id1: 1 }), JSON.stringify({ id1: 1 }))
605+
t.equal(stringify({ id2: 2 }), JSON.stringify({ id2: 2 }))
606+
})
607+
556608
test('allOf: throw Error if types mismatch ', (t) => {
557609
t.plan(1)
558610

0 commit comments

Comments
 (0)