Skip to content

Commit 895112a

Browse files
committed
feat: Fix open-api body bug
1 parent 3976c5e commit 895112a

8 files changed

Lines changed: 57 additions & 34 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@ts-rest/core': major
3+
'@ts-rest/express': major
4+
'@ts-rest/nest': major
5+
'@ts-rest/next': major
6+
'@ts-rest/open-api': major
7+
'@ts-rest/react-query': major
8+
---
9+
10+
Migrate paths to a string rather than function

libs/example-contracts/src/lib/contract-blog.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,24 @@ export interface Post {
99
published: boolean;
1010
tags: string[];
1111
}
12+
13+
const PostSchema = z.object({
14+
id: z.string(),
15+
title: z.string(),
16+
description: z.string().nullable(),
17+
content: z.string().nullable(),
18+
published: z.boolean().nullable(),
19+
tags: z.array(z.string()),
20+
});
21+
1222
const c = initContract();
1323

1424
export const apiBlog = c.router({
1525
createPost: {
1626
method: 'POST',
1727
path: '/posts',
1828
responses: {
19-
201: c.response<Post>(),
29+
201: PostSchema,
2030
},
2131
body: z.object({
2232
title: z.string(),
@@ -29,7 +39,7 @@ export const apiBlog = c.router({
2939
updatePost: {
3040
method: 'PATCH',
3141
path: `/posts/:id`,
32-
responses: { 200: c.response<Post>() },
42+
responses: { 200: PostSchema },
3343
body: z.object({
3444
title: z.string().optional(),
3545
content: z.string().optional(),
@@ -42,8 +52,8 @@ export const apiBlog = c.router({
4252
method: 'DELETE',
4353
path: `/posts/:id`,
4454
responses: {
45-
200: c.response<{ message: string }>(),
46-
404: c.response<{ message: string }>(),
55+
200: z.object({ message: z.string() }),
56+
404: z.object({ message: z.string() }),
4757
},
4858
body: null,
4959
summary: 'Delete a post',
@@ -52,8 +62,8 @@ export const apiBlog = c.router({
5262
method: 'GET',
5363
path: `/posts/:id`,
5464
responses: {
55-
200: c.response<Post>(),
56-
404: c.response<null>(),
65+
200: PostSchema,
66+
404: z.null(),
5767
},
5868
query: null,
5969
summary: 'Get a post by id',
@@ -62,7 +72,7 @@ export const apiBlog = c.router({
6272
method: 'GET',
6373
path: '/posts',
6474
responses: {
65-
200: c.response<{ posts: Post[]; total: number }>(),
75+
200: z.object({ posts: PostSchema.array(), total: z.number() }),
6676
},
6777
query: z.object({
6878
take: z.string().transform(Number).optional(),

libs/ts-rest/core/src/lib/client.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,7 @@ describe('client', () => {
302302

303303
const result = await client.posts.patchPost({
304304
params: { id: '1' },
305+
body: null,
305306
});
306307

307308
expect(result).toStrictEqual({ body: value, status: 200 });
@@ -312,7 +313,7 @@ describe('client', () => {
312313
headers: {
313314
'Content-Type': 'application/json',
314315
},
315-
body: JSON.stringify({}),
316+
body: undefined,
316317
});
317318
});
318319
});

libs/ts-rest/core/src/lib/client.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import { HTTPStatusCode } from './status-codes';
99
import { Without, ZodInferOrType } from './type-utils';
1010

1111
type RecursiveProxyObj<T extends AppRouter> = {
12-
[TKey in keyof T]: T[TKey] extends AppRouter
13-
? RecursiveProxyObj<T[TKey]>
14-
: T[TKey] extends AppRoute
12+
[TKey in keyof T]: T[TKey] extends AppRoute
1513
? DataReturn<T[TKey]>
14+
: T[TKey] extends AppRouter
15+
? RecursiveProxyObj<T[TKey]>
1616
: never;
1717
};
1818

libs/ts-rest/open-api/src/lib/ts-rest-open-api.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,19 @@ const isZodObject = (body: unknown): body is ZodTypeAny => {
3838
return (body as ZodTypeAny)?.safeParse !== undefined;
3939
};
4040

41-
const getResponseSchemaFromZod = (response: unknown) =>
42-
isZodObject(response)
43-
? zodToJsonSchema(response, {
44-
name: 'zodObject',
45-
target: 'openApi3',
46-
}).definitions['zodObject']
47-
: undefined;
41+
const getResponseSchemaFromZod = (response: unknown) => {
42+
const isZodObj = isZodObject(response);
43+
44+
if (!isZodObj) {
45+
return null;
46+
}
47+
const schema = zodToJsonSchema(response, {
48+
name: 'zodObject',
49+
target: 'openApi3',
50+
});
51+
52+
return schema.definitions['zodObject'];
53+
};
4854

4955
export const generateOpenApi = (
5056
router: AppRouter,
@@ -66,8 +72,9 @@ export const generateOpenApi = (
6672
?.map((param) => param.slice(1, -1));
6773

6874
const bodySchema =
69-
path.route?.method !== 'GET' &&
70-
getResponseSchemaFromZod(path.route.responses);
75+
path.route?.method !== 'GET'
76+
? getResponseSchemaFromZod(path.route.body)
77+
: null;
7178

7279
const responses = Object.keys(path.route.responses).reduce((acc, key) => {
7380
const keyAsNumber = Number(key);

libs/ts-rest/react-query/src/lib/ts-rest-client.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ import {
3131
} from '@ts-rest/core';
3232

3333
type RecursiveProxyObj<T extends AppRouter> = {
34-
[TKey in keyof T]: T[TKey] extends AppRouter
35-
? RecursiveProxyObj<T[TKey]>
36-
: T[TKey] extends AppRoute
34+
[TKey in keyof T]: T[TKey] extends AppRoute
3735
? Without<UseQueryArgs<T[TKey]>, never>
36+
: T[TKey] extends AppRouter
37+
? RecursiveProxyObj<T[TKey]>
3838
: never;
3939
};
4040

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"express": "4.18.1",
4040
"graphql": "^16.6.0",
4141
"next": "12.2.5",
42-
"openapi3-ts": "^3.0.1",
42+
"openapi3-ts": "^2.0.2",
4343
"postcss": "^8.4.16",
4444
"prism-react-renderer": "^1.2.1",
4545
"react": "18.2.0",

yarn.lock

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11358,12 +11358,12 @@ open@^8.0.9, open@^8.4.0:
1135811358
is-docker "^2.1.1"
1135911359
is-wsl "^2.2.0"
1136011360

11361-
openapi3-ts@^3.0.1:
11362-
version "3.0.1"
11363-
resolved "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-3.0.1.tgz#8ea3da309b7409146bcd28282a54d193ceb824f4"
11364-
integrity sha512-PddTYhMx/RWOo7yT+XikkVaKwoe4YUDU2GXB6TlK6VJQQyvcezwBGSbCZqscAAdeLvGbpjWbSiPQyc9MSk9s4g==
11361+
openapi3-ts@^2.0.2:
11362+
version "2.0.2"
11363+
resolved "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-2.0.2.tgz#a200dd838bf24c9086c8eedcfeb380b7eb31e82a"
11364+
integrity sha512-TxhYBMoqx9frXyOgnRHufjQfPXomTIHYKhSKJ6jHfj13kS8OEIhvmE8CTuQyKtjjWttAjX5DPxM1vmalEpo8Qw==
1136511365
dependencies:
11366-
yaml "^2.1.1"
11366+
yaml "^1.10.2"
1136711367

1136811368
opener@^1.5.1, opener@^1.5.2:
1136911369
version "1.5.2"
@@ -15307,11 +15307,6 @@ yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2:
1530715307
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
1530815308
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
1530915309

15310-
yaml@^2.1.1:
15311-
version "2.1.1"
15312-
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.1.1.tgz#1e06fb4ca46e60d9da07e4f786ea370ed3c3cfec"
15313-
integrity sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==
15314-
1531515310
yargs-parser@21.0.1, yargs-parser@^21.0.0, yargs-parser@^21.0.1:
1531615311
version "21.0.1"
1531715312
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35"

0 commit comments

Comments
 (0)