Typescript Type Level Custom Error Message, Move Runtime Error to Compile time Error!
Tags: webdev, javascript, typescript, tutorial
Published: 2023-06-26 12:18:06

---
series: Typescript Type Level Custom Error Message
---

Do you want a more descriptive error message rather than usual `type A cannot assign to type B`?

for example, take this function:

```ts
function (a, b, c) => {}
```

**Requirement:**
1. all arguments are number
2. if `a` is 1, `b` cannot be 2
3. if `b` is 2, `c` cannot be 3
4. if `c` is 3, `a` cannot be 1

**Solutions:**
```ts
declare function ABC<A extends number, B extends number, C extends number>(
    a: 1 extends 1 ? C extends 3 ? A extends 1 ? "'a' cannot be 1 if 'c' is 3" : A : A : A,
    b: 1 extends 1 ? A extends 1 ? B extends 2 ? "'b' cannot be 2 if 'a' is 1" : B : B : B,
    c: 1 extends 1 ? B extends 2 ? C extends 3 ? "'c' cannot be 3 if 'b' is 2" : C : C : C
    ): void


ABC(1,2,4) // error at `b`

ABC(4,2,3) // error at `c`

ABC(1,4,3) // error at `a`
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zh8qkf0o4tqnjh0wvjao.png)

[playground](https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwEEAhAYQB4D4QAPDEVYAZ3lWQFsAjEGAGniKq16TFuy694JQXQbNWnbgD4AFACh4G+FABck6cOYBmeAH5C+2fACMp+ACIA5FAfwwUVKhwZ4Xa-CyI8A5gLlhGdvC6lFHwPOqaHDE0MiI2ZgLJBvAATLaOHC5uHl4+CLkBQc7+zFYRugL1cZqu9RYiuWZSmZbGZo4hru6e3r7GFQ4F1Tl1erok8RoAlLoAbjhYwKpbxCTKVjzZPAAsi-AA9GdUMDA4MFreAAYcD9ukykcHPIanF1c3d1BHmAXqodntjl8fpduP97vAHlAHkA)

## How Does It Works?

We use a simple example to demonstrate, where the requirement is `the argument cannot be 1`.
```ts
declare function ABC<A extends number>(
    a: A extends 1? "a cannot be 1": A
    ): void


ABC(1) // error
ABC(2)
```


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9v1yaxvvrb8ax7q7fggf.png)

[playground](https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXygCMwAeAQXhAA8MRVgBneVZAW0JBgD4AKAKHiCCALngVqtekwCMAfngAiKPDBRUqHBngd40haLIChASlEA3HFmB8+RMD2nH4AemeUYMHDFvEeAJmMbPiA)

The formula is 
```ts
condition ? {{type checking}} : Naked Generic Type Parameter
```
or

```ts
condition ? Naked Generic Type Parameter : {{type checking}}
```
As long as you place the [Naked Generic Type Parameter](https://stackoverflow.com/questions/51651499/typescript-what-is-a-naked-type-parameter) in either true case or false case, Typescript can infer the type from the argument where the conditional type is.

It works regardless of a case is reachable or not:
```ts
declare function abc<A>(
    a: A extends never ? A: A & 2
    ): void

abc(1) // error
abc(2)
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nkpmrcy16e8bg9jrix0m.png)

[playground](https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXygCMwAeAQQD4AKAKHnoIC54z4QAPDEVYAZ3lQgAbiBjwA-C3jNWAMngAmGgEpmQnFmA0aAeh279emkTBUAjMvh62MGDhjHiVBcu00gA)

But it still obey the flow of condition, so this will **not** work:
```ts
declare function ABC<A extends number>(
    a: A extends 1?  A :"a cannot be 1"
    ): void

ABC(1) // no error (wrong!)
ABC(2) // error (wrong!)
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8lsq0t6k91bwbl1rsy1z.png)
[playground](https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwEEAhAYQB4D4QAPDEVYAZ3lWQFsAjEGAPgAoAUPGHwoALkJVa9JvACMAfmGUxAIijwwUVKhwZ4XeaqEiAlBIBuOLMAEDiJPnNPwA9K5Y4qMGDhjw+AHdfVABzAEJTe1I+ACYXd29ffyCQiKi7IA)


## Object Literal Type

What if we want to restrict the type of object property?

Take this example where the requirement is property `b` cannot be 2 if property `a` is 1
```ts
declare function ABC<T extends { a: number, b: number }>(
    a: { a: number, b: T['a'] extends 1 ? T['b'] extends 2 ? "'b' cannot be 2 if 'a' is 1" : T : T }
): void

ABC({a:1,b:1}) 
ABC({a:1,b:2}) 
ABC({a:1,b:3}) 

// bad, error at all cases
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gsv7p5gz7abi0twihm0c.png)


It does not work and the logic does not make sense.

**solution:**
```ts
declare function ABC<const T extends { a: number, b: number }>(
    a: [T] extends [T] ? { a: number, b: T['a'] extends 1 ? T['b'] extends 2 ? "'b' cannot be 2 if 'a' is 1" : T['b'] : T['b'] } : T
): void

ABC({ a: 1, b: 1 }) // ok
ABC({ a: 1, b: 2 }) // error!
ABC({ a: 1, b: 3 }) // ok
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mvq0b3blh9xd3txji49m.png)

[playground](https://www.typescriptlang.org/play?#code/CYUwxgNghgTiAEAzArgOzAFwJYHtXwEEAhAYQB4w8BnDeAFXhAA8MRVgr4BveKALnipkAWwBGIGABp4ogULET4AXwB8ACgBQ8bbwEBtOgF1GLNh3gHjAfm67BI8VJkC6egORQ3x5q3acAjPA2rm6iXia+5gBMQfAARKFu8GBQqKg4tOLwMViI8B5JWAFx8C7uYcZlicZKpfQaAJQCAG44WMAaGsQkajz88P7SsgPKDfAA9OPwOADWGpPz44vLC6tL3b12g87ZoxNTEjA4MICA-12km-3bwwDMe5PTM0A)

formula:

```ts
[Naked Generic Type Parameter] extends [Naked Generic Type Parameter] 
? {{type checking }} : Naked Generic Type Parameter
```

**Do note that below will fail:**
```ts
Naked Generic Type Parameter extends Naked Generic Type Parameter 
? {{type checking }} : Naked Generic Type Parameter
```
```ts
declare function ABC<const T extends { a: number, b: number }>(
    a: T extends T ? { a: number, b: T['a'] extends 1 ? T['b'] extends 2 ? "'b' cannot be 2 if 'a' is 1" : T['b'] : T['b'] } : T
): void

ABC({ a: 1, b: 1 }) // ok
ABC({ a: 1, b: 2 }) // no error！!!!
ABC({ a: 1, b: 3 }) // ok
```
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ti4pdzedhdbqx49dju0q.png)
**this will fail too:**
```ts
Naked Generic Type Parameter[] extends Naked Generic Type Parameter[] 
? {{type checking }} : Naked Generic Type Parameter
```
```ts
declare function ABC<const T extends { a: number, b: number }>(
    a: T[] extends T[] ? { a: number, b: T['a'] extends 1 ? T['b'] extends 2 ? "'b' cannot be 2 if 'a' is 1" : T['b'] : T['b'] } : T
): void

ABC({ a: 1, b: 1 }) // ok
ABC({ a: 1, b: 2 }) // no error！!!!
ABC({ a: 1, b: 3 }) // ok
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4q8emxr2mnqrm33m9os1.png)

I am not sure why but probably related to [this](https://dev.to/tylim88/typescript-wtf-moments-the-special-case-t-extends-b-215o)

## Use Case
If you are curious why type level custom error messages are useful, take a look at how [Firelordjs](https://github.com/tylim88/FirelordJS) and [FireSageJS](https://github.com/tylim88/FireSageJS) make Firebase Firestore and Realtime Database much easier to use