Skip to content

Commit b69552f

Browse files
committed
fix(repository-json-schema): fix circular chains when using 'includeRelations'
Signed-off-by: KalleV <kvirtaneva@gmail.com>
1 parent af5f4f9 commit b69552f

3 files changed

Lines changed: 65 additions & 35 deletions

File tree

packages/repository-json-schema/src/__tests__/integration/build-schema.integration.ts

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,19 +1121,12 @@ describe('build-schema', () => {
11211121

11221122
const expectedSchema: JsonSchema = {
11231123
definitions: {
1124-
ProductWithRelations: {
1125-
title: 'ProductWithRelations',
1124+
Product: {
1125+
title: 'Product',
11261126
type: 'object',
1127-
description:
1128-
`(tsType: ProductWithRelations, ` +
1129-
`schemaOptions: { includeRelations: true })`,
11301127
properties: {
11311128
id: {type: 'number'},
11321129
categoryId: {type: 'number'},
1133-
category: {
1134-
$ref: '#/definitions/CategoryWithRelations',
1135-
},
1136-
foreignKey: 'categoryId' as JsonSchema,
11371130
},
11381131
additionalProperties: false,
11391132
},
@@ -1143,7 +1136,7 @@ describe('build-schema', () => {
11431136
products: {
11441137
type: 'array',
11451138
items: {
1146-
$ref: '#/definitions/ProductWithRelations',
1139+
$ref: '#/definitions/Product',
11471140
},
11481141
},
11491142
},
@@ -1175,27 +1168,20 @@ describe('build-schema', () => {
11751168
}
11761169
const expectedSchema: JsonSchema = {
11771170
definitions: {
1178-
ProductWithRelations: {
1179-
title: 'ProductWithRelations',
1171+
Product: {
1172+
title: 'Product',
11801173
type: 'object',
1181-
description:
1182-
`(tsType: ProductWithRelations, ` +
1183-
`schemaOptions: { includeRelations: true })`,
11841174
properties: {
11851175
id: {type: 'number'},
11861176
categoryId: {type: 'number'},
1187-
category: {
1188-
$ref: '#/definitions/CategoryWithoutPropWithRelations',
1189-
},
1190-
foreignKey: 'categoryId' as JsonSchema,
11911177
},
11921178
additionalProperties: false,
11931179
},
11941180
},
11951181
properties: {
11961182
products: {
11971183
type: 'array',
1198-
items: {$ref: '#/definitions/ProductWithRelations'},
1184+
items: {$ref: '#/definitions/Product'},
11991185
},
12001186
},
12011187
additionalProperties: false,
@@ -1213,6 +1199,52 @@ describe('build-schema', () => {
12131199
expect(jsonSchemaWithoutProp).to.deepEqual(expectedSchema);
12141200
});
12151201

1202+
it('does not produce circular $ref chains for bidirectional relations', () => {
1203+
@model()
1204+
class Order extends Entity {
1205+
@property({id: true})
1206+
id: number;
1207+
1208+
@belongsTo(() => Customer)
1209+
customerId: number;
1210+
}
1211+
1212+
@model()
1213+
class Customer extends Entity {
1214+
@property({id: true})
1215+
id: number;
1216+
1217+
@hasMany(() => Order)
1218+
orders?: Order[];
1219+
}
1220+
1221+
const schema = getJsonSchema(Customer, {includeRelations: true});
1222+
1223+
// orders items should reference plain Order, not OrderWithRelations
1224+
expect(schema.properties).to.containDeep({
1225+
orders: {
1226+
type: 'array',
1227+
items: {$ref: '#/definitions/Order'},
1228+
},
1229+
});
1230+
1231+
// The Order definition should be plain — no nested definitions,
1232+
// no customer navigational property
1233+
const orderDef = schema.definitions?.Order as JsonSchema;
1234+
expect(orderDef).to.be.ok();
1235+
expect(orderDef.definitions).to.be.undefined();
1236+
expect(orderDef.properties).to.not.have.property('customer');
1237+
1238+
// No WithRelations keys should appear in definitions
1239+
const defKeys = Object.keys(schema.definitions ?? {});
1240+
for (const key of defKeys) {
1241+
expect(key).to.not.match(
1242+
/WithRelations/,
1243+
`definitions should not contain WithRelations keys, found: ${key}`,
1244+
);
1245+
}
1246+
});
1247+
12161248
it('gets cached JSON schema with relation links if one exists', () => {
12171249
@model()
12181250
class Product extends Entity {

packages/repository-json-schema/src/__tests__/unit/build-schema.unit.ts

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -325,30 +325,27 @@ describe('build-schema', () => {
325325
expect(schema.properties).to.containEql({
326326
children: {
327327
type: 'array',
328-
// The reference here should be `ChildWithRelations`,
329-
// instead of `ParentWithItsChildren`
330-
items: {$ref: '#/definitions/ChildWithRelations'},
328+
// The reference here should be `Child` (not `ParentWithItsChildren`)
329+
// because includeRelations is not cascaded to relation targets.
330+
items: {$ref: '#/definitions/Child'},
331331
},
332332
benchmarkId: {type: 'string'},
333333
color: {type: 'string'},
334334
});
335-
// The recursive calls should NOT inherit
336-
// `title` from the previous call's `options`.
337-
// So the `title` here is `ChildWithRelations`
338-
// instead of `ParentWithItsChildren`.
335+
// The recursive calls should NOT inherit `title` from the previous
336+
// call's `options`, and `includeRelations` is not propagated to
337+
// relation targets. So the definition is plain `Child`.
339338
expect(schema.definitions).to.containEql({
340-
ChildWithRelations: {
341-
title: 'ChildWithRelations',
339+
Child: {
340+
title: 'Child',
342341
type: 'object',
343-
description:
344-
'(tsType: ChildWithRelations, schemaOptions: { includeRelations: true })',
345342
properties: {name: {type: 'string'}},
346343
additionalProperties: false,
347344
},
348345
});
349346
});
350347

351-
it('includeRelations is not propagated to properties decorated with @property()', () => {
348+
it('includeRelations is not propagated to nested properties or relation targets', () => {
352349
@model()
353350
class FooModel extends Entity {
354351
@property({
@@ -379,14 +376,14 @@ describe('build-schema', () => {
379376
name: {
380377
type: 'string',
381378
},
382-
// Decorated with @property() Model should NOT be 'WithRelations'
379+
// Decorated with @property() — no includeRelations propagation
383380
foo: {
384381
$ref: '#/definitions/FooModel',
385382
},
386-
// Decorated with @hasMany() Model should be 'WithRelations'
383+
// Decorated with @hasMany() — also no includeRelations propagation
387384
relatedFoo: {
388385
type: 'array',
389-
items: {$ref: '#/definitions/FooModelWithRelations'},
386+
items: {$ref: '#/definitions/FooModel'},
390387
},
391388
});
392389
});

packages/repository-json-schema/src/build-schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,7 @@ export function modelToJsonSchema<T extends object>(
570570
// when generating the relation or property schemas
571571
const targetOptions = {...options};
572572
delete targetOptions.title;
573+
delete targetOptions.includeRelations;
573574

574575
const targetSchema = getJsonSchema(targetType, targetOptions);
575576
const targetRef = {$ref: `#/definitions/${targetSchema.title}`};

0 commit comments

Comments
 (0)