Custom type errors and input validation in TypeScript 2.8
One of the latest additions in TypeScript is conditional types, a category of types that let us choose one of two possible values based on a condition. The syntax for these types is equivalent to the ternary operator in JavaScript.
In this post we’ll learn:
- How to create predicate types using conditional types
- How to use predicate types to validate the inputs to our functions
- A clever trick to present custom validation messages in type errors
Predicate types
Predicate types *not to be confused with type predicates* are types that evaluate to either their generic parameter or never
. They’re mainly used in conjunction with other types to produce a check or validation of some kind. For the purpose of our demonstration we will create a safe division function that doesn’t accept 0 as its second argument to prevent division by zero errors. Let’s first define that function without validation.
We are using generics so that TypeScript can evaluate the argument types as literals instead of just the plain number type. Our first step is to create a predicate type that checks if a number is 0. Then we’ll use that type as the return type of our function.
Now our function will return never
if the second argument is zero, which is better than nothing, but it’s still not sound enough. There will be a type error, but it will be where the return value is used, not where the function is called.
Input validation
We want to move the NotZero
validation in front of the function so that it will be ran at call time when the generics are evaluated. Should be as simple as an extends clause, right? There is a catch though, an expression like T extends NotZero<T>
is not allowed in TypeScript. You will be presented with Type parameter ‘T’ has a circular constraint.
There is a trick to overcome this restriction using default generic parameters. It looks like this:
Our generic type parameter U
is just a T
in disguise because of it’s default value and this allows our code to compile. Using this technique we can move the validation upfront.
While our logic is working and the compiler gives us an error, the error is not very friendly. We don’t know why the type validation failed, we only have a never
which is not helpful at all.
Custom error messages
This is really more of a bonus tip since it’s just a trick that I found that can be cleverly used to show custom error messages.
Instead of evaluating to never
in our predicate type, we can evaluate to an error message.
Now our type error looks like this:
While it’s not the cleanest solution, I believe that there are use cases for the techniques in this post.
Thanks for reading! For more about conditional types you can look here. You can also check out my stuff at GitHub 😄