Skip to content

Commit 4d1c23e

Browse files
author
Lucas Treffenstädt
committed
correctly infer types for application/json with charset specifier
1 parent afde9eb commit 4d1c23e

File tree

2 files changed

+70
-4
lines changed

2 files changed

+70
-4
lines changed

src/types.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ export type OpenapiPaths<Paths> = {
1313
}
1414
}
1515

16+
type ApplicationJson = 'application/json' | `application/json;${string}`
17+
18+
type JsonResponse<T> = [T] extends [Record<string, unknown>]
19+
? {
20+
[K in keyof T]: K extends ApplicationJson ? T[K] : never
21+
}[keyof T]
22+
: unknown
23+
1624
export type OpArgType<OP> = OP extends {
1725
parameters?: {
1826
path?: infer P
@@ -23,12 +31,13 @@ export type OpArgType<OP> = OP extends {
2331
}
2432
// openapi 3
2533
requestBody?: {
26-
content: {
27-
'application/json': infer RB
28-
}
34+
content: infer RB
2935
}
3036
}
31-
? P & Q & (B extends Record<string, unknown> ? B[keyof B] : unknown) & RB
37+
? P &
38+
Q &
39+
(B extends Record<string, unknown> ? B[keyof B] : unknown) &
40+
JsonResponse<RB>
3241
: Record<string, never>
3342

3443
type OpResponseTypes<OP> = OP extends {

test/infer.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,26 @@ type Op3 = Omit<paths3['/v1/account_links']['post'], 'requestBody'> & {
2323
}
2424
}
2525

26+
type Op4 = Omit<paths3['/v1/account_links']['post'], 'requestBody'> & {
27+
requestBody: {
28+
content: {
29+
'application/json;charset=utf-8': paths3['/v1/account_links']['post']['requestBody']['content']['application/x-www-form-urlencoded']
30+
}
31+
}
32+
responses: {
33+
200: {
34+
content: {
35+
'application/json;charset=UTF-8': paths3['/v1/account_links']['post']['responses']['200']['content']['application/json']
36+
}
37+
}
38+
default: {
39+
content: {
40+
'application/json; charset=UTF-8': paths3['/v1/account_links']['post']['responses']['default']['content']['application/json']
41+
}
42+
}
43+
}
44+
}
45+
2646
interface Openapi2 {
2747
Argument: OpArgType<Op2>
2848
Return: OpReturnType<Op2>
@@ -37,6 +57,13 @@ interface Openapi3 {
3757
Error: Pick<OpErrorType<Op3>['data']['error'], 'type' | 'message'>
3858
}
3959

60+
interface Openapi4 {
61+
Argument: OpArgType<Op4>
62+
Return: OpReturnType<Op4>
63+
Default: Pick<OpDefaultReturnType<Op4>['error'], 'type' | 'message'>
64+
Error: Pick<OpErrorType<Op4>['data']['error'], 'type' | 'message'>
65+
}
66+
4067
type Same<A, B> = A extends B ? (B extends A ? true : false) : false
4168

4269
describe('infer', () => {
@@ -128,4 +155,34 @@ describe('infer', () => {
128155
const err: Err = { data: { error: {} } } as any
129156
expect(err.data.error.charge).toBeUndefined()
130157
})
158+
159+
describe('application/json with charset', () => {
160+
it('argument', () => {
161+
const same: Same<Openapi4['Argument'], Openapi3['Argument']> = true
162+
expect(same).toBe(true)
163+
164+
// @ts-expect-error -- missing properties
165+
const arg: Openapi4['Argument'] = {}
166+
expect(arg.account).toBeUndefined()
167+
})
168+
169+
it('return', () => {
170+
const same: Same<Openapi4['Return'], Openapi3['Return']> = true
171+
expect(same).toBe(true)
172+
173+
// @ts-expect-error -- missing properties
174+
const ret: Openapi4['Return'] = {}
175+
expect(ret.url).toBeUndefined()
176+
})
177+
178+
it('default', () => {
179+
const same: Same<Openapi4['Default'], Openapi3['Default']> = true
180+
expect(same).toBe(true)
181+
})
182+
183+
it('error', () => {
184+
const same: Same<Openapi4['Error'], Openapi3['Error']> = true
185+
expect(same).toBe(true)
186+
})
187+
})
131188
})

0 commit comments

Comments
 (0)