Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: Enum Key in Mapped Types

Tags:

I have an enum for http methods:

export enum HttpMethod {
  GET = 'GET', POST = 'POST', /*...*/
}

Then I define a basic method type, that can have any HttpMethod as key:

type Methods = {
  [M in HttpMethod]?: any;
};

A basic Route type could use this Method type:

type Route<M extends Methods = any> = {
  methods: M;
}

So I can define any route like:

interface AnyRoute extends Route<{
  [HttpMethod.GET]: AnyRequestHandler;
}> {}

So far so good. Now I want to add a Validator:

type Validator<R extends Route, M extends HttpMethod> = {/*...*/}

And only want to allow adding Methods to the Validator, that are defined in the Route:

type RouteMethodValidators<R extends Route> = {
  [M in keyof R['methods']]?: Validator<R, M>;
};

Although my IDE seems to understand it, I get the following errors:

  • Type 'M' does not satisfy the constrain 'HttpMethod'.
  • Type 'keyof R["methods"]' is not assignable to type 'HttpMethod'.

Is there any way I can tell typescript, that this is is definitely a member of HttpMethod?

like image 494
Sascha Galley Avatar asked Mar 01 '18 08:03

Sascha Galley


1 Answers

Your problem mostly lies here: type Route<M extends Methods = any>

First of all, a default value any will result in M being of type string in RouteMethodValidator because Route<any>['methods'] is any and keyof any is string.

Now, changing the default value to Methods still won't solve the problem because you do M extends Methods which basically means that M can have more keys than the ones defined in Methods, i.e. more than are defined in HttpMethods. But in Validator you only allow values of HttpMethods.

I believe your best option would be to make Route not generic.

type Route = {
  methods: Methods;
}

type RouteMethodValidators<R extends Route> = {
  [M in HttpMethod]?: Validator<R, M>;
}
like image 120
Tao Avatar answered Sep 19 '22 05:09

Tao