Skip to content

Commit ac2e936

Browse files
ymc9claude
andcommitted
fix(server): use proper OpenAPI Response Objects for error responses and add fetch-related response schemas
Error responses were using `$ref` to schemas directly in response positions, which is invalid per OpenAPI spec. Replace with inline Response Objects containing description and content. Also add proper response schema references to fetch-related endpoints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4e7cad4 commit ac2e936

3 files changed

Lines changed: 58 additions & 19 deletions

File tree

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

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ type SchemaObject = OpenAPIV3_1.SchemaObject;
1818
type ReferenceObject = OpenAPIV3_1.ReferenceObject;
1919
type ParameterObject = OpenAPIV3_1.ParameterObject;
2020

21+
const ERROR_RESPONSE = {
22+
description: 'Error',
23+
content: {
24+
'application/vnd.api+json': {
25+
schema: { $ref: '#/components/schemas/_errorResponse' },
26+
},
27+
},
28+
};
29+
2130
const SCALAR_STRING_OPS = ['$contains', '$icontains', '$search', '$startsWith', '$endsWith'];
2231
const SCALAR_COMPARABLE_OPS = ['$lt', '$lte', '$gt', '$gte'];
2332
const SCALAR_ARRAY_OPS = ['$has', '$hasEvery', '$hasSome', '$isEmpty'];
@@ -171,7 +180,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
171180
},
172181
},
173182
},
174-
'400': { $ref: '#/components/schemas/_errorResponse' },
183+
'400': ERROR_RESPONSE,
175184
},
176185
};
177186

@@ -196,7 +205,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
196205
},
197206
},
198207
},
199-
'400': { $ref: '#/components/schemas/_errorResponse' },
208+
'400': ERROR_RESPONSE,
200209
},
201210
};
202211

@@ -229,7 +238,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
229238
},
230239
},
231240
},
232-
'404': { $ref: '#/components/schemas/_errorResponse' },
241+
'404': ERROR_RESPONSE,
233242
},
234243
};
235244
}
@@ -257,8 +266,8 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
257266
},
258267
},
259268
},
260-
'400': { $ref: '#/components/schemas/_errorResponse' },
261-
'404': { $ref: '#/components/schemas/_errorResponse' },
269+
'400': ERROR_RESPONSE,
270+
'404': ERROR_RESPONSE,
262271
},
263272
};
264273
}
@@ -271,7 +280,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
271280
parameters: [idParam],
272281
responses: {
273282
'200': { description: 'Deleted successfully' },
274-
'404': { $ref: '#/components/schemas/_errorResponse' },
283+
'404': ERROR_RESPONSE,
275284
},
276285
};
277286
}
@@ -305,8 +314,17 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
305314
operationId: `get${modelName}_${fieldName}`,
306315
parameters: params,
307316
responses: {
308-
'200': { description: `Related ${fieldDef.type} resource(s)` },
309-
'404': { $ref: '#/components/schemas/_errorResponse' },
317+
'200': {
318+
description: `Related ${fieldDef.type} resource(s)`,
319+
content: {
320+
'application/vnd.api+json': {
321+
schema: isCollection
322+
? { $ref: `#/components/schemas/${fieldDef.type}ListResponse` }
323+
: { $ref: `#/components/schemas/${fieldDef.type}Response` },
324+
},
325+
},
326+
},
327+
'404': ERROR_RESPONSE,
310328
},
311329
},
312330
};
@@ -339,7 +357,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
339357
description: `${fieldName} relationship`,
340358
content: { 'application/vnd.api+json': { schema: relSchemaRef } },
341359
},
342-
'404': { $ref: '#/components/schemas/_errorResponse' },
360+
'404': ERROR_RESPONSE,
343361
},
344362
},
345363
put: {
@@ -353,7 +371,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
353371
},
354372
responses: {
355373
'200': { description: 'Relationship updated' },
356-
'400': { $ref: '#/components/schemas/_errorResponse' },
374+
'400': ERROR_RESPONSE,
357375
},
358376
},
359377
patch: {
@@ -367,7 +385,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
367385
},
368386
responses: {
369387
'200': { description: 'Relationship updated' },
370-
'400': { $ref: '#/components/schemas/_errorResponse' },
388+
'400': ERROR_RESPONSE,
371389
},
372390
},
373391
};
@@ -388,7 +406,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
388406
},
389407
responses: {
390408
'200': { description: 'Added to relationship collection' },
391-
'400': { $ref: '#/components/schemas/_errorResponse' },
409+
'400': ERROR_RESPONSE,
392410
},
393411
};
394412
}
@@ -403,7 +421,7 @@ export class RestApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
403421
operationId: `proc_${procName}`,
404422
responses: {
405423
'200': { description: `Result of ${procName}` },
406-
'400': { $ref: '#/components/schemas/_errorResponse' },
424+
'400': ERROR_RESPONSE,
407425
},
408426
};
409427

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ import type { OpenApiSpecOptions } from '../common/types';
1616
type SchemaObject = OpenAPIV3_1.SchemaObject;
1717
type ReferenceObject = OpenAPIV3_1.ReferenceObject;
1818

19+
const ERROR_RESPONSE = {
20+
description: 'Error',
21+
content: {
22+
'application/json': {
23+
schema: { $ref: '#/components/schemas/_ErrorResponse' },
24+
},
25+
},
26+
};
27+
1928
/**
2029
* Generates OpenAPI v3.1 specification for the RPC-style CRUD API.
2130
*/
@@ -126,7 +135,7 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
126135
},
127136
responses: {
128137
'200': { description: 'Transaction results' },
129-
'400': { $ref: '#/components/schemas/_ErrorResponse' },
138+
'400': ERROR_RESPONSE,
130139
},
131140
},
132141
};
@@ -178,7 +187,7 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
178187
},
179188
},
180189
},
181-
'400': { $ref: '#/components/schemas/_ErrorResponse' },
190+
'400': ERROR_RESPONSE,
182191
},
183192
};
184193
}
@@ -210,7 +219,7 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
210219
},
211220
},
212221
},
213-
'400': { $ref: '#/components/schemas/_ErrorResponse' },
222+
'400': ERROR_RESPONSE,
214223
},
215224
};
216225
}
@@ -242,7 +251,7 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
242251
},
243252
},
244253
},
245-
'400': { $ref: '#/components/schemas/_ErrorResponse' },
254+
'400': ERROR_RESPONSE,
246255
},
247256
};
248257
}
@@ -274,7 +283,7 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
274283
},
275284
},
276285
},
277-
'400': { $ref: '#/components/schemas/_ErrorResponse' },
286+
'400': ERROR_RESPONSE,
278287
},
279288
};
280289
}
@@ -286,7 +295,7 @@ export class RPCApiSpecGenerator<Schema extends SchemaDef = SchemaDef> {
286295
operationId: `proc_${procName}`,
287296
responses: {
288297
'200': { description: `Result of ${procName}` },
289-
'400': { $ref: '#/components/schemas/_ErrorResponse' },
298+
'400': ERROR_RESPONSE,
290299
},
291300
};
292301

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,18 @@ describe('REST OpenAPI spec generation', () => {
124124
expect(spec.paths['/post/{id}/comments']).toBeDefined();
125125
});
126126

127+
it('fetch related path has response schema', () => {
128+
// Collection relation: should reference ListResponse
129+
const collectionPath = spec.paths['/user/{id}/posts'] as any;
130+
const collectionSchema = collectionPath.get.responses['200'].content['application/vnd.api+json'].schema;
131+
expect(collectionSchema.$ref).toBe('#/components/schemas/PostListResponse');
132+
133+
// Singular relation: should reference Response
134+
const singularPath = spec.paths['/post/{id}/setting'] as any;
135+
const singularSchema = singularPath.get.responses['200'].content['application/vnd.api+json'].schema;
136+
expect(singularSchema.$ref).toBe('#/components/schemas/SettingResponse');
137+
});
138+
127139
it('relationship path has correct methods', () => {
128140
const relPath = spec.paths['/user/{id}/relationships/posts'];
129141
expect(relPath.get).toBeDefined();

0 commit comments

Comments
 (0)