Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion app/config/openapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,20 @@ const securitySchemes = {

const errorResponse = {
type: 'object',
// Shape emitted by the global error handler
// (app/middleware/error-handler.js) and every controller's 4xx /
// 5xx exit: a `message` string plus an optional `requestId` for
// log correlation. The `error` field declared here previously
// never appeared at runtime — the handler deliberately suppresses
// raw error detail (see tests/unit/controller-error-shape.test.js
// for the policy) so SDK code-gen that consumed this schema was
// building clients with a field that never landed.
properties: {
message: { type: 'string' },
error: { type: 'string' },
requestId: {
type: 'string',
description: 'UUID correlator (same value as the X-Request-Id response header); only present when the request reached the request-id middleware.',
},
},
required: ['message'],
};
Expand Down
22 changes: 22 additions & 0 deletions tests/api/openapi.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,28 @@ describe('OpenAPI spec', () => {
expect(schemas.TimeEntry.properties.teStartedAt).toBeDefined();
});

test('Error component schema matches the runtime shape ({message, requestId?})', async () => {
// Pre-#334 the schema declared a free-form `error: string` field
// that the runtime never emitted (controller-error-shape.test.js
// pins the no-leak policy; the error-handler only ever sends
// `{message, requestId?}`). SDK code-gen consuming the spec was
// building clients that read a non-existent field. The replacement
// pins the true runtime shape: required `message`, optional
// `requestId`.
const res = await request(app).get('/openapi.json');
const err = res.body.components.schemas.Error;
expect(err.type).toBe('object');
expect(err.properties.message).toBeDefined();
expect(err.properties.message.type).toBe('string');
// requestId IS declared; `error` was dropped.
expect(err.properties.requestId).toBeDefined();
expect(err.properties.requestId.type).toBe('string');
expect(err.properties.error).toBeUndefined();
// message is the only required field — requestId only appears
// when the request reached the request-id middleware.
expect(err.required).toEqual(['message']);
});

test('POST /v1/customer/bulk 201 declares the {message, count, customers} envelope', async () => {
// makeBulkCreate (app/controllers/_bulk-helpers.js) emits
// {message, count, <createdKey>}. The spec previously had
Expand Down