-
-
Notifications
You must be signed in to change notification settings - Fork 528
[openapi-fetch] Support parseAs type inference #1442
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,6 +39,15 @@ export type QuerySerializer<T> = ( | |
export type BodySerializer<T> = (body: OperationRequestBodyContent<T>) => any; | ||
|
||
export type ParseAs = "json" | "text" | "blob" | "arrayBuffer" | "stream"; | ||
export type ParseAsResponse<T, K extends ParseAs> = K extends "text" | ||
? Awaited<ReturnType<Response["text"]>> | ||
: K extends "blob" | ||
? Awaited<ReturnType<Response["blob"]>> | ||
: K extends "arrayBuffer" | ||
? Awaited<ReturnType<Response["arrayBuffer"]>> | ||
: K extends "stream" | ||
? Awaited<ReturnType<Response["body"]>> | ||
: T; | ||
|
||
export interface DefaultParamsOption { | ||
params?: { | ||
|
@@ -62,9 +71,20 @@ export type RequestBodyOption<T> = OperationRequestBodyContent<T> extends never | |
|
||
export type FetchOptions<T> = RequestOptions<T> & Omit<RequestInit, "body">; | ||
|
||
export type FetchResponse<T> = | ||
/** This type helper makes the 2nd function param required if params/requestBody are required; otherwise, optional */ | ||
export type MaybeOptionalInit< | ||
P extends {}, | ||
M extends keyof P, | ||
> = HasRequiredKeys<FetchOptions<FilterKeys<P, M>>> extends never | ||
? [(FetchOptions<FilterKeys<P, M>> | undefined)?] | ||
: [FetchOptions<FilterKeys<P, M>>]; | ||
|
||
export type FetchResponse<T, R extends ParseAs = "json"> = | ||
| { | ||
data: FilterKeys<SuccessResponse<ResponseObjectMap<T>>, MediaType>; | ||
data: ParseAsResponse< | ||
FilterKeys<SuccessResponse<ResponseObjectMap<T>>, MediaType>, | ||
R | ||
>; | ||
error?: never; | ||
response: Response; | ||
} | ||
|
@@ -86,155 +106,107 @@ export default function createClient<Paths extends {}>( | |
clientOptions?: ClientOptions, | ||
): { | ||
/** Call a GET endpoint */ | ||
GET<P extends PathsWithMethod<Paths, "get">>( | ||
GET< | ||
P extends PathsWithMethod<Paths, "get">, | ||
I extends MaybeOptionalInit<Paths[P], "get">, | ||
>( | ||
url: P, | ||
...init: HasRequiredKeys< | ||
FetchOptions<FilterKeys<Paths[P], "get">> | ||
> extends never | ||
? [(FetchOptions<FilterKeys<Paths[P], "get">> | undefined)?] | ||
: [FetchOptions<FilterKeys<Paths[P], "get">>] | ||
...init: I | ||
): Promise< | ||
FetchResponse< | ||
"get" extends infer T | ||
? T extends "get" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was autogenerated TypeScript defs, not something I think is necessary. I cleaned this up to be more readable, and at least in the tests I didn’t notice any regressions |
||
? T extends keyof Paths[P] | ||
? Paths[P][T] | ||
: unknown | ||
: never | ||
: never | ||
Paths[P]["get"], | ||
I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems like there’s a way to avoid this, but I couldn’t think of a way. I needed this in my version for TS to fall back to |
||
> | ||
>; | ||
/** Call a PUT endpoint */ | ||
PUT<P extends PathsWithMethod<Paths, "put">>( | ||
PUT< | ||
P extends PathsWithMethod<Paths, "put">, | ||
I extends MaybeOptionalInit<Paths[P], "put">, | ||
>( | ||
url: P, | ||
...init: HasRequiredKeys< | ||
FetchOptions<FilterKeys<Paths[P], "put">> | ||
> extends never | ||
? [(FetchOptions<FilterKeys<Paths[P], "put">> | undefined)?] | ||
: [FetchOptions<FilterKeys<Paths[P], "put">>] | ||
...init: I | ||
): Promise< | ||
FetchResponse< | ||
"put" extends infer T | ||
? T extends "put" | ||
? T extends keyof Paths[P] | ||
? Paths[P][T] | ||
: unknown | ||
: never | ||
: never | ||
Paths[P]["put"], | ||
I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" | ||
> | ||
>; | ||
/** Call a POST endpoint */ | ||
POST<P extends PathsWithMethod<Paths, "post">>( | ||
POST< | ||
P extends PathsWithMethod<Paths, "post">, | ||
I extends MaybeOptionalInit<Paths[P], "post">, | ||
>( | ||
url: P, | ||
...init: HasRequiredKeys< | ||
FetchOptions<FilterKeys<Paths[P], "post">> | ||
> extends never | ||
? [(FetchOptions<FilterKeys<Paths[P], "post">> | undefined)?] | ||
: [FetchOptions<FilterKeys<Paths[P], "post">>] | ||
...init: I | ||
): Promise< | ||
FetchResponse< | ||
"post" extends infer T | ||
? T extends "post" | ||
? T extends keyof Paths[P] | ||
? Paths[P][T] | ||
: unknown | ||
: never | ||
: never | ||
Paths[P]["post"], | ||
I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" | ||
> | ||
>; | ||
/** Call a DELETE endpoint */ | ||
DELETE<P extends PathsWithMethod<Paths, "delete">>( | ||
DELETE< | ||
P extends PathsWithMethod<Paths, "delete">, | ||
I extends MaybeOptionalInit<Paths[P], "delete">, | ||
>( | ||
url: P, | ||
...init: HasRequiredKeys< | ||
FetchOptions<FilterKeys<Paths[P], "delete">> | ||
> extends never | ||
? [(FetchOptions<FilterKeys<Paths[P], "delete">> | undefined)?] | ||
: [FetchOptions<FilterKeys<Paths[P], "delete">>] | ||
...init: I | ||
): Promise< | ||
FetchResponse< | ||
"delete" extends infer T | ||
? T extends "delete" | ||
? T extends keyof Paths[P] | ||
? Paths[P][T] | ||
: unknown | ||
: never | ||
: never | ||
Paths[P]["delete"], | ||
I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" | ||
> | ||
>; | ||
/** Call a OPTIONS endpoint */ | ||
OPTIONS<P extends PathsWithMethod<Paths, "options">>( | ||
OPTIONS< | ||
P extends PathsWithMethod<Paths, "options">, | ||
I extends MaybeOptionalInit<Paths[P], "options">, | ||
>( | ||
url: P, | ||
...init: HasRequiredKeys< | ||
FetchOptions<FilterKeys<Paths[P], "options">> | ||
> extends never | ||
? [(FetchOptions<FilterKeys<Paths[P], "options">> | undefined)?] | ||
: [FetchOptions<FilterKeys<Paths[P], "options">>] | ||
...init: I | ||
): Promise< | ||
FetchResponse< | ||
"options" extends infer T | ||
? T extends "options" | ||
? T extends keyof Paths[P] | ||
? Paths[P][T] | ||
: unknown | ||
: never | ||
: never | ||
Paths[P]["options"], | ||
I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" | ||
> | ||
>; | ||
/** Call a HEAD endpoint */ | ||
HEAD<P extends PathsWithMethod<Paths, "head">>( | ||
HEAD< | ||
P extends PathsWithMethod<Paths, "head">, | ||
I extends MaybeOptionalInit<Paths[P], "head">, | ||
>( | ||
url: P, | ||
...init: HasRequiredKeys< | ||
FetchOptions<FilterKeys<Paths[P], "head">> | ||
> extends never | ||
? [(FetchOptions<FilterKeys<Paths[P], "head">> | undefined)?] | ||
: [FetchOptions<FilterKeys<Paths[P], "head">>] | ||
...init: I | ||
): Promise< | ||
FetchResponse< | ||
"head" extends infer T | ||
? T extends "head" | ||
? T extends keyof Paths[P] | ||
? Paths[P][T] | ||
: unknown | ||
: never | ||
: never | ||
Paths[P]["head"], | ||
I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" | ||
> | ||
>; | ||
/** Call a PATCH endpoint */ | ||
PATCH<P extends PathsWithMethod<Paths, "patch">>( | ||
PATCH< | ||
P extends PathsWithMethod<Paths, "patch">, | ||
I extends MaybeOptionalInit<Paths[P], "patch">, | ||
>( | ||
url: P, | ||
...init: HasRequiredKeys< | ||
FetchOptions<FilterKeys<Paths[P], "patch">> | ||
> extends never | ||
? [(FetchOptions<FilterKeys<Paths[P], "patch">> | undefined)?] | ||
: [FetchOptions<FilterKeys<Paths[P], "patch">>] | ||
...init: I | ||
): Promise< | ||
FetchResponse< | ||
"patch" extends infer T | ||
? T extends "patch" | ||
? T extends keyof Paths[P] | ||
? Paths[P][T] | ||
: unknown | ||
: never | ||
: never | ||
Paths[P]["patch"], | ||
I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" | ||
> | ||
>; | ||
/** Call a TRACE endpoint */ | ||
TRACE<P extends PathsWithMethod<Paths, "trace">>( | ||
TRACE< | ||
P extends PathsWithMethod<Paths, "trace">, | ||
I extends MaybeOptionalInit<Paths[P], "trace">, | ||
>( | ||
url: P, | ||
...init: HasRequiredKeys< | ||
FetchOptions<FilterKeys<Paths[P], "trace">> | ||
> extends never | ||
? [(FetchOptions<FilterKeys<Paths[P], "trace">> | undefined)?] | ||
: [FetchOptions<FilterKeys<Paths[P], "trace">>] | ||
...init: I | ||
): Promise< | ||
FetchResponse< | ||
"trace" extends infer T | ||
? T extends "trace" | ||
? T extends keyof Paths[P] | ||
? Paths[P][T] | ||
: unknown | ||
: never | ||
: never | ||
Paths[P]["trace"], | ||
I[0]["parseAs"] extends ParseAs ? I[O]["parseAs"] : "json" | ||
> | ||
>; | ||
}; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The object was much cleaner, however, I was not able to get that working.
This is messier, but it does have the advantage of always falling back to the original type
T
whereas the cleaner object could returnunknown