Typescript any number except X
Asked Answered
C

1

6

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?

Cellini answered 16/6, 2021 at 19:6 Comment(0)
C
7

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>
Countryandwestern answered 16/6, 2021 at 19:16 Comment(3)
It's not really an infinite set. Numbers in JavaScript are 64-bit floats so there are a finite (very large but finite) number of possible values. So it seems possible that #15480 might be implemented one day. Although it is much simpler to consider number to be an infinite set (as the compiler currently does).Ammieammine
While not wrong, it is conceptually infinite, storing all possible values of number in memory would take over 17GB (4,294,967,295*32/8). If Typescript implements negated types, this case would definitely not implemented by treating number as a finite union.Countryandwestern
There would be no need to store every value. You could use a range or a sequence of ranges. Although I don't immediately see how the finite nature of floats would affect that implementation. I mean, if you're using ranges, then does it matter if there are finite or infinite values within the range? So you're right. It probably doesn't matter.Ammieammine

© 2022 - 2024 — McMap. All rights reserved.