Search code examples
typescriptgenericstypescript-typingsdiscriminator

Typescript any number except X


TLDR:

Is this type possible somehow in TS? Exclude<number, 200 | 400> ("any number except 200 or 400")

I have the following use case. I have a response type, which is generic:

type HttpResponse<Body = any, StatusCode = number> = {
  body: Body
  statusCode: StatusCode
}

And I'd like to use the status code as a discriminator:

// Succes
type SuccessResponse = HttpResponse<SomeType, 200>
// Known error
type ClientErrorResponse = HttpResponse<ClientError, 400>
// Anything else, generic error, issue is with the status code here.
type OtherErrorResponse = HttpResponse<GenericError, Exclude<number, 200 | 400>>

// The response type is a union of the above
type MyResponse = SuccessResponse | ClientErrorResponse | OtherErrorResponse 

When I use the MyResponse type, I'd like to use the status code as a discriminator, eg.:

const response: MyResponse = ...

if(response.statusCode === 200) {
  // response is inferred as SuccessResponse => body is inferred as SomeType
} else if(response.statusCode === 400) {
  // response is inferred as ClientErrorResponse => body is inferred as ClientError
} else {
  // response is inferred as OtherErrorResponse => body is inferred as GenericError
}

However it doesn't work like this as Exclude<number, 200 | 400> is the same as just number. How do I solve this? Is the type "any number except 200 or 400" possible with typescript? Any other creative solutions?


Solution

  • This is not possible at the moment.

    See: https://github.com/microsoft/TypeScript/issues/15480


    Exclude<number, 200 | 400> doesn't work because typescript tracks only what something is, and never tracks what it is not. To so make excluding some values from an infinite series work, typescript would have to generate a union with every single possible value, except for the ones you wish to exclude. This would be a union of infinite length (since Infinity minus 2 equals Infinity)


    That said, the list of http status codes is actually finite. So the best way is probably to create a union of all possible values and use that:

    type HTTPStatusCode = 100 | 101 | 102 | 103 | 200 | 201 | 202 | ...
    

    Now this should work fine:

    Exclude<HTTPStatusCode, 200 | 400>