Reputation: 145
Is it possible to get the type of parameters of methods from a generic?
For example if I have:
interface Keys {
create: any;
...
}
type MethodNames<T> = { [P in keyof Keys]: keyof T; }
Then is it possible to get the type of parameters of those Methods?
type MethodParams<T> = { [P in keyof Keys]: Parameters<T[???]>; }
Playground Link to Relevant Code
Upvotes: 1
Views: 691
Reputation: 328152
The only way I can imagine this working is if MethodParams
is not only generic in T
, the type of the underlying API, but also in M
, the particular MethodNames<T>
appropriate for T
. And that could look like this:
type Keys = "create" | "getOne" | "getAll" | "update" | "delete"
export type MethodNames<T> = {
[K in Keys]: keyof T;
};
export type MethodParams<T, M extends MethodNames<T>> = {
[K in Keys]: T[M[K]] extends (...args: infer P) => any ? P : never;
};
(Note that I make Keys
just a union of string literal types instead of making it an object type with values of type any
that we ignore.)
So MethodParams<T, M>
iterates over the keys in Keys
and then looks up each key in M
to get the key of T
we want to probe for parameters. That is, if K
is a member of Keys
, we want to get the parameters from what we hope is a function type at T[M[K]]
. We use conditional type inference to get that.
Let's test to see that it works. First I'll write a helper function to verify that a proposed method mapper works for a particular type T
:
const methods = <T, M extends MethodNames<T>>(api: T, methods: M) => methods;
And now I'll make up a fake api:
interface SomeData {
id: number,
a: string,
b: number,
c: boolean
}
interface SomeApi {
change(id: number, data: Partial<SomeData>): SomeData,
destroy(id: number): boolean
grab(id: number): SomeData | undefined,
grabbingSpree(): SomeData[],
make(data: Omit<SomeData, "id">): SomeData,
}
declare const someApi: SomeApi;
Here's the method mapper:
const someApiMethods = methods(someApi, {
create: "make",
getOne: "grab",
getAll: "grabbingSpree",
update: "change",
delete: "destroy"
})
And now I can finally try MethodParams
:
type SomeApiMethodParams = MethodParams<SomeApi, typeof someApiMethods>;
/* type SomeApiMethodParams = {
create: [data: Omit<SomeData, "id">];
getOne: [id: number];
getAll: [];
update: [id: number, data: Partial<SomeData>];
delete: [id: number];
} */
Looks good. The type of SomeApiMethodParams
is what we expect it to be.
Oh, and this also means that any class or type that needs to compute MethodParams
will need to be generic in the appropriate MethodNames
type. For example, your AbstractTestEnv
class would be augmented with another type parameter:
export abstract class AbstractTestEnv<S, T, U, M extends MethodNames<S>> {
public api: S;
public createDto: T;
public crudMethods: M;
protected constructor(api: S, crudMethods: M) {
this.api = api;
this.crudMethods = crudMethods;
this.createDto = this.generateCreateDto(this.resourceId);
}
public abstract generateCreateDto(resourceId: string): T;
public abstract getParams(): MethodParams<S, M>;
/* snip */
}
Upvotes: 1