Skip to content

Commit

Permalink
Merge pull request #67 from lifeomic/enforce-response-type
Browse files Browse the repository at this point in the history
  • Loading branch information
swain authored Jun 8, 2023
2 parents 0ac55e3 + 8bfc07e commit 81e6a3a
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 3 deletions.
54 changes: 52 additions & 2 deletions src/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,58 @@ describe('input validation', () => {
});
});

describe('output validation', () => {
(['GET', 'DELETE', 'POST', 'PUT', 'PATCH'] as const).forEach((method) => {
test(`${method} requests throw an Error when response type does not match the schema`, async () => {
const { client } = setup((router) =>
router
.declare({
name: 'createItem',
route: `${method} /items`,
request: z.object({}),
response: z.object({ message: z.string() }),
})
.implement(`${method} /items`, () => ({
// @ts-expect-error Intentionally writing incorrect TS here
message: 123,
})),
);

const { status } = await client.request({
method,
url: '/items',
});

expect(status).toStrictEqual(500);
});

test(`${method} requests allow extra fields on response values and strip them from responses`, async () => {
const { client } = setup((router) =>
router
.declare({
name: 'createItem',
route: `${method} /items`,
request: z.object({}),
response: z.object({ message: z.string() }),
})
.implement(`${method} /items`, () => ({
message: 'test-message',
anotherField: 1234,
})),
);

const { status, data } = await client.request({
method,
url: '/items',
});

expect(status).toStrictEqual(200);
// Ensure `anotherField` was stripped.
expect(data).toStrictEqual({ message: 'test-message' });
});
});
});

describe('implementations', () => {
(['POST', 'PUT', 'PATCH'] as const).forEach((method) => {
test(`rejects requests that do not match the schema for ${method} requests`, async () => {
Expand Down Expand Up @@ -471,8 +523,6 @@ describe('introspection', () => {

const introspectionResult = await client.get('/private/introspection');

console.log(JSON.stringify(introspectionResult.data, null, 2));

expect(introspectionResult.data.schema).toStrictEqual({
Endpoints: {
'POST /items': {
Expand Down
12 changes: 11 additions & 1 deletion src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,17 @@ export class OneSchemaRouter<
}
return res.data;
},
implementation,
async (ctx) => {
const result = await implementation(ctx);
const res = endpoint.response.safeParse(result);
if (!res.success) {
const friendlyError = fromZodError(res.error, {
prefix: `A response value from endpoint '${route}' did not conform to the response schema.`,
});
return ctx.throw(500, friendlyError.message);
}
return res.data;
},
);

return this;
Expand Down

0 comments on commit 81e6a3a

Please sign in to comment.