T. Kallup
T. Kallup

Reputation: 371

Typescript function parameter type based on another parameter

I'll try my best in describing our case, as I have no idea how to title this.

We have a package, which handles requesting our services/cloud functions (if for example service A needs something done by service B, it uses this package). This package exposes a function to call each service (for example for service auth, there would be a auth function exposed. We have a definition of available endpoints for each service and as this grows, it's harder to maintain since we were unable to figure out a way to type the parameters for the requests.

What I wanted to do, is simply have another type which would contain the parameter types for each endpoint of each service. For example we have endpoint called getEmail, which would return email of a user and requires an id parameter. Type params type would look like this:

type Params = {
  getEmail: {
    id: number;
  }
}

Very simplified version of the code:

type FunctionName = 'foo' | 'bar' | 'baz';

type FunctionParams = {
  foo: {
    myVar: number;
  };
  bar: {
    myVar: string;
  };
  baz: Record<string, number>;
};

declare const sendRequest: (...args: any[]) => any;

const callFunction = (
  fn: FunctionName,
  params: FunctionParams[typeof fn],
  //                     ^^^^^^^^^ What should I put here?
) => {
  sendRequest(fn, params);
};

callFunction('foo', {
  myVar: 'this should fail',
  // This is allowed, as the type of the second parameter is:
  // { myVar: number; } | { myVar: string; } | Record<string, number>) => void
  // I want it to be { myVar: number; }
});

Upvotes: 19

Views: 15077

Answers (2)

Eduardo Cuomo
Eduardo Cuomo

Reputation: 19006

May be, you will need other kind of implementation, like following:


/**
 * API Functions Mapping.
 */
interface ApiFunctions {
  foo: {
    myVar: number;
  };
  bar: {
    myVar: string;
  };
  baz: Record<string, number>;
}

/**
 * Generic API Functions method.
 *
 * @param args
 * @returns
 */
declare function sendRequest(...args: any[]): any;

/**
 * Call {@link sendRequest} wrapper.
 *
 * @param fn
 * @param params
 */
function callFunction<T extends keyof ApiFunctions>(
  fn: T,
  params: ApiFunctions[typeof fn]
) {
  sendRequest(fn, params);
}

callFunction('foo', {
  myVar: 'this should FAIL', // <<<--- FAIL!
});

callFunction('bar', {
  myVar: 'this should WORK',
});

Probably the following article can help you: https://typeofnan.dev/how-to-make-one-function-argument-dependent-on-another-in-typescript/

Upvotes: 1

ddprrt
ddprrt

Reputation: 7584

You want to use generics! You can parameterize callFunction to bind fn to a specific string, and then use this to index FunctionParams:

function callFunction<T extends FunctionName>(
  fn: T,
  params: FunctionParams[T],
) {
  sendRequest(fn, params);
};

callFunction('foo', {
  myVar: 'this should fail', //BOOM!
});

Also, don't waste energy maintaining types. FunctionName can be as easy as

type FunctionName = keyof FunctionParams

with that, every time you add new params, FunctionName gets updated. See also this playground

Upvotes: 27

Related Questions