Skip to content

Commit f547bb4

Browse files
committed
Implement response
1 parent 898c96f commit f547bb4

File tree

5 files changed

+158
-14
lines changed

5 files changed

+158
-14
lines changed

examples/vite/openapi.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,13 +168,31 @@
168168
}
169169
}
170170
}
171+
},
172+
"404": {
173+
"description": "User not found",
174+
"content": {
175+
"application/json": {
176+
"schema": {
177+
"$ref": "#/components/schemas/Error"
178+
}
179+
}
180+
}
171181
}
172182
}
173183
}
174184
}
175185
},
176186
"components": {
177187
"schemas": {
188+
"Error": {
189+
"type": "object",
190+
"properties": {
191+
"message": {
192+
"type": "string"
193+
}
194+
}
195+
},
178196
"Post": {
179197
"type": "object",
180198
"properties": {

examples/vite/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function App() {
1919
// query: { postId: 1 },
2020
})
2121
.then((res) => {
22-
console.log(res)
22+
console.log(res.data?.[0]?.createdAt, res.error?.message)
2323
})
2424

2525
api

packages/core/src/additional.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,8 @@ export type DevupApiRequestInit = Omit<RequestInit, 'body'> & {
1010
body?: object | RequestInit['body']
1111
params?: Record<string, string | number | boolean | null | undefined>
1212
}
13+
14+
// biome-ignore lint/suspicious/noExplicitAny: any is used to allow for flexibility in the type
15+
export type ExtractValue<T, V extends string, F = any> = V extends keyof T
16+
? T[V]
17+
: F

packages/fetch/src/api.ts

Lines changed: 90 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,25 @@ import type {
1313
DevupPostApiStructKey,
1414
DevupPutApiStruct,
1515
DevupPutApiStructKey,
16+
ExtractValue,
1617
RequiredOptions,
1718
} from '@devup-api/core'
19+
import { convertResponse } from './response-converter'
1820
import { getUrlWithMethod } from './url-map'
1921
import { getApiEndpoint, isPlainObject } from './utils'
2022

23+
type DevupApiResponse<T, E = unknown> =
24+
| {
25+
data: T
26+
error?: undefined
27+
response: Response
28+
}
29+
| {
30+
data?: undefined
31+
error: E
32+
response: Response
33+
}
34+
2135
export class DevupApi {
2236
private baseUrl: string
2337
private defaultOptions: DevupApiRequestInit
@@ -34,8 +48,13 @@ export class DevupApi {
3448
path: T,
3549
...options: [RequiredOptions<O>] extends [never]
3650
? [options?: DevupApiRequestInit]
37-
: [options: DevupApiRequestInit & Omit<O, 'response'>]
38-
) {
51+
: [options: DevupApiRequestInit & Omit<O, 'response' | 'error'>]
52+
): Promise<
53+
DevupApiResponse<
54+
ExtractValue<O, 'response'>,
55+
ExtractValue<O, 'error', unknown>
56+
>
57+
> {
3958
return this.request(path, {
4059
method: 'GET',
4160
...options[0],
@@ -50,7 +69,12 @@ export class DevupApi {
5069
...options: [RequiredOptions<O>] extends [never]
5170
? [options?: DevupApiRequestInit]
5271
: [options: DevupApiRequestInit & Omit<O, 'response'>]
53-
) {
72+
): Promise<
73+
DevupApiResponse<
74+
ExtractValue<O, 'response'>,
75+
ExtractValue<O, 'error', unknown>
76+
>
77+
> {
5478
return this.request(path, {
5579
method: 'GET',
5680
...options[0],
@@ -65,7 +89,12 @@ export class DevupApi {
6589
...options: [RequiredOptions<O>] extends [never]
6690
? [options?: DevupApiRequestInit]
6791
: [options: DevupApiRequestInit & Omit<O, 'response'>]
68-
) {
92+
): Promise<
93+
DevupApiResponse<
94+
ExtractValue<O, 'response'>,
95+
ExtractValue<O, 'error', unknown>
96+
>
97+
> {
6998
return this.request(path, {
7099
method: 'POST',
71100
...options[0],
@@ -80,7 +109,12 @@ export class DevupApi {
80109
...options: [RequiredOptions<O>] extends [never]
81110
? [options?: DevupApiRequestInit]
82111
: [options: DevupApiRequestInit & Omit<O, 'response'>]
83-
) {
112+
): Promise<
113+
DevupApiResponse<
114+
ExtractValue<O, 'response'>,
115+
ExtractValue<O, 'error', unknown>
116+
>
117+
> {
84118
return this.request(path, {
85119
method: 'POST',
86120
...options[0],
@@ -95,7 +129,12 @@ export class DevupApi {
95129
...options: [RequiredOptions<O>] extends [never]
96130
? [options?: DevupApiRequestInit]
97131
: [options: DevupApiRequestInit & Omit<O, 'response'>]
98-
) {
132+
): Promise<
133+
DevupApiResponse<
134+
ExtractValue<O, 'response'>,
135+
ExtractValue<O, 'error', unknown>
136+
>
137+
> {
99138
return this.request(path, {
100139
method: 'PUT',
101140
...options[0],
@@ -110,7 +149,12 @@ export class DevupApi {
110149
...options: [RequiredOptions<O>] extends [never]
111150
? [options?: DevupApiRequestInit]
112151
: [options: DevupApiRequestInit & Omit<O, 'response'>]
113-
) {
152+
): Promise<
153+
DevupApiResponse<
154+
ExtractValue<O, 'response'>,
155+
ExtractValue<O, 'error', unknown>
156+
>
157+
> {
114158
return this.request(path, {
115159
method: 'PUT',
116160
...options[0],
@@ -125,7 +169,12 @@ export class DevupApi {
125169
...options: [RequiredOptions<O>] extends [never]
126170
? [options?: DevupApiRequestInit]
127171
: [options: DevupApiRequestInit & Omit<O, 'response'>]
128-
) {
172+
): Promise<
173+
DevupApiResponse<
174+
ExtractValue<O, 'response'>,
175+
ExtractValue<O, 'error', unknown>
176+
>
177+
> {
129178
return this.request(path, {
130179
method: 'DELETE',
131180
...options[0],
@@ -140,7 +189,12 @@ export class DevupApi {
140189
...options: [RequiredOptions<O>] extends [never]
141190
? [options?: DevupApiRequestInit]
142191
: [options: DevupApiRequestInit & Omit<O, 'response'>]
143-
) {
192+
): Promise<
193+
DevupApiResponse<
194+
ExtractValue<O, 'response'>,
195+
ExtractValue<O, 'error', unknown>
196+
>
197+
> {
144198
return this.request(path, {
145199
method: 'DELETE',
146200
...options[0],
@@ -155,7 +209,12 @@ export class DevupApi {
155209
...options: [RequiredOptions<O>] extends [never]
156210
? [options?: DevupApiRequestInit]
157211
: [options: DevupApiRequestInit & Omit<O, 'response'>]
158-
) {
212+
): Promise<
213+
DevupApiResponse<
214+
ExtractValue<O, 'response'>,
215+
ExtractValue<O, 'error', unknown>
216+
>
217+
> {
159218
return this.request(path, {
160219
method: 'PATCH',
161220
...options[0],
@@ -170,7 +229,12 @@ export class DevupApi {
170229
...options: [RequiredOptions<O>] extends [never]
171230
? [options?: DevupApiRequestInit]
172231
: [options: DevupApiRequestInit & Omit<O, 'response'>]
173-
) {
232+
): Promise<
233+
DevupApiResponse<
234+
ExtractValue<O, 'response'>,
235+
ExtractValue<O, 'error', unknown>
236+
>
237+
> {
174238
return this.request(path, {
175239
method: 'PATCH',
176240
...options[0],
@@ -182,7 +246,12 @@ export class DevupApi {
182246
...options: [RequiredOptions<O>] extends [never]
183247
? [options?: DevupApiRequestInit]
184248
: [options: DevupApiRequestInit & Omit<O, 'response'>]
185-
) {
249+
): Promise<
250+
DevupApiResponse<
251+
ExtractValue<O, 'response'>,
252+
ExtractValue<O, 'error', unknown>
253+
>
254+
> {
186255
const { method, url } = getUrlWithMethod(path)
187256
const mergedOptions = {
188257
...this.defaultOptions,
@@ -195,7 +264,7 @@ export class DevupApi {
195264
if (requestOptions.body && isPlainObject(requestOptions.body)) {
196265
requestOptions.body = JSON.stringify(requestOptions.body)
197266
}
198-
return fetch(
267+
const request = new Request(
199268
getApiEndpoint(
200269
this.baseUrl,
201270
url,
@@ -210,6 +279,14 @@ export class DevupApi {
210279
),
211280
requestOptions as RequestInit,
212281
)
282+
return fetch(request).then((response) =>
283+
convertResponse(request, response),
284+
) as Promise<
285+
DevupApiResponse<
286+
ExtractValue<O, 'response'>,
287+
ExtractValue<O, 'error', unknown>
288+
>
289+
>
213290
}
214291

215292
setDefaultOptions(options: DevupApiRequestInit) {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* OPENAPI-TYPESCRIPT
3+
* @param request
4+
* @param response
5+
* @param parseAs
6+
* @returns
7+
*/
8+
export async function convertResponse(
9+
request: Request,
10+
response: Response,
11+
parseAs: 'stream' | 'json' | 'text' = 'json',
12+
): Promise<{
13+
data?: unknown | undefined
14+
error?: unknown | undefined
15+
response: Response
16+
}> {
17+
if (
18+
response.status === 204 ||
19+
request.method === 'HEAD' ||
20+
response.headers.get('Content-Length') === '0'
21+
) {
22+
return response.ok
23+
? { data: undefined, response }
24+
: { error: undefined, response }
25+
}
26+
27+
// parse response (falling back to .text() when necessary)
28+
if (response.ok) {
29+
// if "stream", skip parsing entirely
30+
if (parseAs === 'stream') {
31+
return { data: response.body, response }
32+
}
33+
return { data: await response[parseAs](), response }
34+
}
35+
36+
// handle errors
37+
let error = await response.text()
38+
try {
39+
error = JSON.parse(error) // attempt to parse as JSON
40+
} catch {
41+
// noop
42+
}
43+
return { error, response }
44+
}

0 commit comments

Comments
 (0)