Skip to content

Commit 4e7cad4

Browse files
ymc9claude
andcommitted
fix(server): hide relations to excluded models in REST OpenAPI spec and remove unnecessary as any casts
- Skip relation paths, read schema fields, and create/update relationship entries when the related model is excluded by slicing - Replace `as any` with `as OpenAPIV3_1.PathItemObject` where possible and remove unnecessary cast on `generateSharedParams()` Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 76b634c commit 4e7cad4

3 files changed

Lines changed: 31 additions & 17 deletions

File tree

packages/server/src/api/rest/openapi.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getMetaDescription,
88
isFieldOmitted,
99
isFilterKindIncluded,
10+
isModelIncluded,
1011
isOperationIncluded,
1112
isProcedureIncluded,
1213
} from '../common/spec-utils';
@@ -60,7 +61,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
6061
paths: this.generatePaths(),
6162
components: {
6263
schemas: this.generateSchemas(),
63-
parameters: this.generateSharedParams() as any,
64+
parameters: this.generateSharedParams(),
6465
},
6566
} as OpenAPIV3_1.Document;
6667
}
@@ -103,6 +104,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
103104
// Relation paths
104105
for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
105106
if (!fieldDef.relation) continue;
107+
if (!isModelIncluded(fieldDef.type, this.queryOptions)) continue;
106108
const relModelDef = this.schema.models[fieldDef.type];
107109
if (!relModelDef) continue;
108110
const relIdFields = this.getIdFields(relModelDef);
@@ -114,15 +116,15 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
114116
fieldName,
115117
fieldDef,
116118
tag,
117-
) as any;
119+
);
118120

119121
// Relationship management path
120122
paths[`/${modelPath}/{id}/relationships/${fieldName}`] = this.buildRelationshipPath(
121123
modelName,
122124
fieldName,
123125
fieldDef,
124126
tag,
125-
) as any;
127+
);
126128
}
127129
}
128130

@@ -131,15 +133,15 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
131133
for (const [procName, procDef] of Object.entries(this.schema.procedures)) {
132134
if (!isProcedureIncluded(procName, this.queryOptions)) continue;
133135
const isMutation = !!procDef.mutation;
134-
const pathItem: Record<string, any> = {};
135-
136136
if (isMutation) {
137-
pathItem['post'] = this.buildProcedureOperation(procName, 'post');
137+
paths[`/${PROCEDURE_ROUTE_PREFIXES}/${procName}`] = {
138+
post: this.buildProcedureOperation(procName, 'post'),
139+
} as OpenAPIV3_1.PathItemObject;
138140
} else {
139-
pathItem['get'] = this.buildProcedureOperation(procName, 'get');
141+
paths[`/${PROCEDURE_ROUTE_PREFIXES}/${procName}`] = {
142+
get: this.buildProcedureOperation(procName, 'get'),
143+
} as OpenAPIV3_1.PathItemObject;
140144
}
141-
142-
paths[`/${PROCEDURE_ROUTE_PREFIXES}/${procName}`] = pathItem as any;
143145
}
144146
}
145147

@@ -702,6 +704,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
702704
for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) {
703705
if (fieldDef.omit) continue;
704706
if (isFieldOmitted(modelName, fieldName, this.queryOptions)) continue;
707+
if (fieldDef.relation && !isModelIncluded(fieldDef.type, this.queryOptions)) continue;
705708

706709
const schema = this.fieldToSchema(fieldDef);
707710
const fieldDescription = getMetaDescription(fieldDef.attributes);
@@ -738,6 +741,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
738741
if (fieldDef.foreignKeyFor) continue;
739742
// Skip auto-generated id fields
740743
if (idFieldNames.has(fieldName) && fieldDef.default !== undefined) continue;
744+
if (fieldDef.relation && !isModelIncluded(fieldDef.type, this.queryOptions)) continue;
741745

742746
if (fieldDef.relation) {
743747
relationships[fieldName] = fieldDef.array
@@ -799,6 +803,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
799803
if (fieldDef.omit) continue;
800804
if (fieldDef.updatedAt) continue;
801805
if (fieldDef.foreignKeyFor) continue;
806+
if (fieldDef.relation && !isModelIncluded(fieldDef.type, this.queryOptions)) continue;
802807

803808
if (fieldDef.relation) {
804809
relationships[fieldName] = fieldDef.array

packages/server/src/api/rpc/openapi.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
6868
const argsSchemaName = `${modelName}${this.opToArgsSchema(op)}`;
6969
paths[`/${modelPath}/${op}`] = {
7070
get: this.buildGetOperation(modelName, op, tag, argsSchemaName),
71-
} as any;
71+
} as OpenAPIV3_1.PathItemObject;
7272
}
7373

7474
// Write operations
@@ -137,15 +137,15 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
137137
if (!isProcedureIncluded(procName, this.queryOptions)) continue;
138138

139139
const isMutation = !!procDef.mutation;
140-
const pathItem: Record<string, any> = {};
141-
142140
if (isMutation) {
143-
pathItem['post'] = this.buildProcedureOperation(procName, 'post');
141+
paths[`/${PROCEDURE_ROUTE_PREFIXES}/${procName}`] = {
142+
post: this.buildProcedureOperation(procName, 'post'),
143+
} as OpenAPIV3_1.PathItemObject;
144144
} else {
145-
pathItem['get'] = this.buildProcedureOperation(procName, 'get');
145+
paths[`/${PROCEDURE_ROUTE_PREFIXES}/${procName}`] = {
146+
get: this.buildProcedureOperation(procName, 'get'),
147+
} as OpenAPIV3_1.PathItemObject;
146148
}
147-
148-
paths[`/${PROCEDURE_ROUTE_PREFIXES}/${procName}`] = pathItem as any;
149149
}
150150
}
151151

@@ -457,7 +457,7 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
457457

458458
// Unique fields
459459
for (const [uniqueName, uniqueInfo] of Object.entries(modelDef.uniqueFields)) {
460-
if (typeof (uniqueInfo as any).type === 'string') {
460+
if ('type' in uniqueInfo && typeof uniqueInfo.type === 'string') {
461461
// Single unique field
462462
const fieldDef = modelDef.fields[uniqueName];
463463
if (fieldDef && !properties[uniqueName]) {

packages/server/test/openapi/rest-openapi.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,15 @@ describe('REST OpenAPI spec generation - queryOptions', () => {
265265
expect(s.paths?.['/user']).toBeDefined();
266266
expect(s.paths?.['/post']).toBeUndefined();
267267
expect(s.components?.schemas?.['Post']).toBeUndefined();
268+
269+
// Relation paths to excluded model should not exist
270+
expect(s.paths?.['/user/{id}/posts']).toBeUndefined();
271+
expect(s.paths?.['/user/{id}/relationships/posts']).toBeUndefined();
272+
273+
// Relation fields to excluded model should not appear in read schema
274+
const userSchema = s.components?.schemas?.['User'] as any;
275+
expect(userSchema.properties['posts']).toBeUndefined();
276+
expect(userSchema.properties['email']).toBeDefined();
268277
});
269278

270279
it('slicing includedModels limits models in spec', async () => {

0 commit comments

Comments
 (0)