Issue
Sorry for the confusing title! I'll try to be as clear as possible here. Given the following interface (generated by openapi-typescript
as an API definition):
TypeScript playground to see this in action
export interface paths {
'/v1/some-path/:id': {
get: {
parameters: {
query: {
id: number;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
post: {
parameters: {
body: {
name: string;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
};
}
The above interface paths
will have many paths identified by a string, each having some methods available which then define the parameters and response type.
I am trying to write a generic apiCall
function, such that given a path and a method knows the types of the parameters required, and the return type.
This is what I have so far:
type Path = keyof paths;
type PathMethods<P extends Path> = keyof paths[P];
type RequestParams<P extends Path, M extends PathMethods<P>> =
paths[P][M]['parameters'];
type ResponseType<P extends Path, M extends PathMethods<P>> =
paths[P][M]['responses'][200]['schema'];
export const apiCall = (
path: Path,
method: PathMethods<typeof path>,
params: RequestParams<typeof path, typeof method>
): Promise<ResponseType<typeof path, typeof method>> => {
const url = path;
console.log('params', params);
// method & url are
return fetch(url, { method }) as any;
};
However this won't work properly and I get the following errors:
paths[P][M]['parameters']['path']
->Type '"parameters"' cannot be used to index type 'paths[P][M]'
Even though it does work (If I dotype test = RequestParams<'/v1/some-path/:id', 'get'>
thentest
shows the correct type)
Any idea how to achieve this?
Solution
Solution
After few trials, this is the solution I found.
First, I used a conditional type to define RequestParams
:
type RequestParams<P extends Path, M extends PathMethods<P>> =
"parameters" extends keyof paths[P][M]
? paths[P][M]["parameters"]
: undefined;
Because typescript deduces the type of path
on the fly, the key parameters
may not exist so we cant use it. The conditional type checks this specific case.
The same can be done for ResponseType
(which will be more verbose) to access the properties typescript is complaining about.
Then, I updated the signature of the function apiCall
:
export const apiCall = <P extends Path, M extends PathMethods<P>>(
path: P,
method: M,
params: RequestParams<P, M>
): Promise<ResponseType<P, M>> => {
//...
};
So now the types P
and M
are tied together.
Bonus
Finally, in case there is no parameter needed, I made the parameter params
optional using a conditional type once again:
export const apiCall = <P extends Path, M extends PathMethods<P>>(
path: P,
method: M,
...params: RequestParams<P, M> extends undefined ? []: [RequestParams<P, M>]
): Promise<ResponseType<P, M>> => {
//...
};
Here is a working typescript playground with the solution. I added a method delete
with no params just to test the final use case.
Sources
- https://www.typescriptlang.org/docs/handbook/2/conditional-types.html
- https://stackoverflow.com/a/52318137/6325822
Edit
Here the updated typescript playground with on errors.
Also, I saw that Alessio's solution only works for one path which is a bit limitating. The one I suggest has no errors and works for any number of paths.
Answered By - Baboo
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.