Skip to content

Commit dbd66f2

Browse files
authored
Merge pull request #23 from cuppachino/support-formdata
fix issue 22 - allow sending FormData as body
2 parents 53f37cc + 456668e commit dbd66f2

File tree

3 files changed

+38
-14
lines changed

3 files changed

+38
-14
lines changed

.changeset/brave-icons-type.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cuppachino/openapi-fetch": minor
3+
---
4+
5+
fix issue 22 - allow sending FormData as body

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"editor.formatOnSave": false,
33
"editor.codeActionsOnSave": {
4-
"source.fixAll.eslint": true
4+
"source.fixAll.eslint": "explicit"
55
},
66
"typescript.tsdk": "node_modules/typescript/lib",
77
"files.exclude": {

src/index.ts

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,18 @@ export type OpArgType<OP> = OP extends {
2323
}
2424
// openapi 3
2525
requestBody?: {
26-
content: {
27-
'application/json': infer RB
28-
}
26+
content:
27+
| {
28+
'application/json': infer RB
29+
}
30+
| {
31+
'multipart/form-data': infer FD
32+
}
2933
}
3034
}
31-
? P & Q & (B extends Record<string, unknown> ? B[keyof B] : unknown) & RB
35+
? FD extends Record<string, string>
36+
? FormData
37+
: P & Q & (B extends Record<string, unknown> ? B[keyof B] : unknown) & RB
3238
: Record<string, never>
3339

3440
type OpResponseTypes<OP> = OP extends {
@@ -65,11 +71,11 @@ export type OpDefaultReturnType<OP> = _OpDefaultReturnType<OpResponseTypes<OP>>
6571
const never: unique symbol = Symbol()
6672

6773
type _OpErrorType<T> = {
68-
[S in Exclude<keyof T, 200 | 201>]: {
74+
[S in Exclude<keyof T, 200 | 201 | 204>]: {
6975
status: S extends 'default' ? typeof never : S
7076
data: T[S]
7177
}
72-
}[Exclude<keyof T, 200 | 201>]
78+
}[Exclude<keyof T, 200 | 201 | 204>]
7379

7480
type Coalesce<T, D> = [T] extends [never] ? D : T
7581

@@ -236,10 +242,14 @@ function getQuery(
236242
return queryString(queryObj)
237243
}
238244

239-
function getHeaders(body?: string, init?: HeadersInit) {
245+
function getHeaders(body?: CustomRequestInit['body'], init?: HeadersInit) {
240246
const headers = new Headers(init)
241247

242-
if (body !== undefined && !headers.has('Content-Type')) {
248+
if (
249+
body !== undefined &&
250+
!(body instanceof FormData) &&
251+
!headers.has('Content-Type')
252+
) {
243253
headers.append('Content-Type', 'application/json')
244254
}
245255

@@ -250,8 +260,13 @@ function getHeaders(body?: string, init?: HeadersInit) {
250260
return headers
251261
}
252262

253-
function getBody(method: Method, payload: any) {
254-
const body = sendBody(method) ? JSON.stringify(payload) : undefined
263+
function getBody(method: Method, payload: unknown): CustomRequestInit['body'] {
264+
if (!sendBody(method)) {
265+
return
266+
}
267+
268+
const body = payload instanceof FormData ? payload : JSON.stringify(payload)
269+
255270
// if delete don't send body if empty
256271
return method === 'delete' && body === '{}' ? undefined : body
257272
}
@@ -272,7 +287,10 @@ function mergeRequestInit(
272287
return { ...first, ...second, headers }
273288
}
274289

275-
function getFetchParams(request: Request) {
290+
function getFetchParams(request: Request): {
291+
url: string
292+
init: CustomRequestInit
293+
} {
276294
// clone payload
277295
// if body is a top level array [ 'a', 'b', param: value ] with param values
278296
// using spread [ ...payload ] returns [ 'a', 'b' ] and skips custom keys
@@ -288,7 +306,8 @@ function getFetchParams(request: Request) {
288306
const headers = getHeaders(body, request.init?.headers)
289307
const url = request.baseUrl + path + query
290308

291-
const init = {
309+
// @ts-expect-error `body` is the correct type, but because we're using exact optional types, `body` is not allowed to be explicitly `undefined`. It's inferred as `undefined` because of the union return type of `getBody`, and there's no way to tell TS that it's optional here.
310+
const init: CustomRequestInit = {
292311
...request.init,
293312
method: request.method.toUpperCase(),
294313
headers,
@@ -429,7 +448,7 @@ function fetcher<Paths>() {
429448
init: mergeRequestInit(defaultInit, init),
430449
fetch,
431450
}),
432-
)) as CreateFetch<M, Paths[P][M]>,
451+
)) as unknown as CreateFetch<M, Paths[P][M]>,
433452
}),
434453
}),
435454
}

0 commit comments

Comments
 (0)