Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typing An Api Calling Function With Generics

I'm trying to make a function that can call multiple APIs and gives strong typing for each argument: api - the name of the api, route - the desired route within 'api', and params - a JSON of the parameters required by the api and route chosen.

I've created an example of what I'd like to in a TS Playground here.

Here's the code:

type APINames = "google" | "yahoo";

type Routes<A extends APINames> = {
    google: "endpoint1" | "endpoint2";
    yahoo: "endpoint3";
}[A];

type RouteParams<A extends APINames, R extends Routes<A>> = {
    google: {
        endpoint1: {
            name: string;
        };
        endpoint2: {
            otherName: string;
        };
    };
    yahoo: {
        endpoint3: {
            otherOtherName: string;
        };
    };
}[A][R];

const execute = <A extends APINames, R extends Routes<A>>(api: A, route: R, params: RouteParams<A, R>) => {
    // ... code
}

execute('google', 'endpoint1', {
  name: 'George Washington'  
});

I appear to be getting a type error on my RouteParams type because while [A] is a legal first index, [R] isn't a legal second index because R is now the type 'endpoint1' | 'endpoint2' | 'endpoint3' and some subset of those don't exist on each key within the RouteParams object. The code above actually gives the desired typing help when using the execute function; I just need to resolve the TS error on RouteParams. I suspect there's a utility type that would solve this and I'm also open to solutions that are a completely different structure.

The ultimate goal is to have a function that takes 3 arguments, the first is an api name from a union type, the second is a route from a union type associated with the api name, and the third is a json parameters object associated with the api name and route combination.

like image 346
dinx Avatar asked Mar 09 '26 17:03

dinx


1 Answers

The approach I'd take is to extract the object type you've mentioned in RouteParams into its own interface:

interface ApiMap {
    google: {
        endpoint1: {
            name: string;
        };
        endpoint2: {
            otherName: string;
        };
    };
    yahoo: {
        endpoint3: {
            otherOtherName: string;
        };
    };
}

And then describe the operation explicitly in terms of generic keyof and indexed access types:

const execute = <A extends keyof ApiMap, R extends keyof ApiMap[A]>(
    api: A, route: R, params: ApiMap[A][R] // no error
) => {    // ... code
}

That compiles with no error and it still behaves as desired from the caller's side:

execute('google', 'endpoint1', { name: 'George Washington' });
execute('google', 'endpoint2', { otherName: 'Abraham Lincoln' });
execute('yahoo', 'endpoint3', { otherOtherName: 'one of them Roosevelts or something' });

Playground link to code

like image 51
jcalz Avatar answered Mar 12 '26 05:03

jcalz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!