Type Driven Development: Errors as Values

Written by

in

Part 10 – Errors as Values

This is a series of posts I’m writing about using types as another tool in software development, Continuous Delivery, & keeping LLM’s honest. They’re also a design & refactoring tool, a communication tool, and reduce how many tests you have to write.

Parts
  1. Part 1 – Branded Types
  2. Part 2 – Product Types
  3. Part 3 – Union & Discriminated Unions
  4. Part 4 – Non-Empty Collections
  5. Part 5 – Indexed Types
  6. Part 6 – unknown vs any
  7. Part 7 – Result
  8. Part 8 – Schema
  9. Part 9 – Total Function
  10. Part 10 – Errors as Values
  11. Part 11 – Property Tests
  12. Part 12 – Type Proofs
  13. Part 13 – Exhaustiveness Checking
  14. Part 14 – Parse, Don’t Validate
  15. Part 15 – Anti-Corruption Layer
  16. Part 16 – Opaque Types
  17. Part 17 – Maybe
  18. Part 18 – Smart Constructors
  19. Part 19 – Pipeline
  20. Part 20 – Railway Oriented Programming
  21. Part 21 – Typestate
  22. Part 22 – Capabilities
  23. Part 23 – Immutability
  24. Part 24 – Making Impossible States Impossible
  25. Part 25 – Type Driven Development: How to do it
  26. Part 26 – Final Thoughts

Errors as Values moves errors from a random, runtime consequence to an iterative design process using types. u intentionally think about & design what could go wrong w/your code in functions, libraries/back-ends u integrate with & your application. Let’s fix fetch to see how this all works.

We’ve talked about the 4 error types in the past:

  1. domain
  2. infrastructure
  3. panic
  4. unforeseen

If u don’t know where an Error goes in the above list, 1st can u or the user do anything about it? If no, then 3.

2nd, ask your Product/Business Owner. If they look confused it’s 2, else it is 1.

fetch is infrastructure; we use it to make http calls, but it’s painful to work w/; Promise<Response> as a return value lies, having to check response.ok first, _then_ attempt response.json(), etc. Numerous exceptions, order of operation issues, none of it type-safe. 1 thing can go right: Your schema passes. Let’s narrow what can go wrong: only 4 things: it timed out, got a 4xx/5xx status, a network http 0 error, or your schema failed. Let’s model that as a Discriminated Union to narrow what can go wrong:

type FetchError = Timeout | BadStatus | NetworkErr | BadBody

Why not just a Union? When we get a BadStatus, what was the status code? When we get a BadBody was it the JSON failing to parse, or it parsed by didn’t match the schema, & on what part(s)? DU’s hold data + ensure we only access that data for that particular type.

type Timeout = { tag: 'Timeout', time: Seconds }
type BadStatus = { tag: 'BadStatus', code: HttpStatusCode }
type NetworkErr = { ... }
type BadBody = { tag: 'BadBody', zodErrors: ZodError[] }Code language: JavaScript (javascript)

Now we can write a more total, safer function to fetch data:

type get = <T>(s:ZodSchema<T>, url:string) =>
  Result<T, FetchError>Code language: HTML, XML (xml)

Then you wrap fetch with code to convert to our 4 errors:

try {

  const response = fetch(...)

  // handle bad status
  if(!response.ok) {
    return { tag: 'BadStatus', code: response.status }

  // handle timeouts
  if(error.name === 'AbortError') {
    return { tag: 'Timeout', ... }Code language: JavaScript (javascript)

Benefits of this hard work? Fetch is no longer imperative & its now type-safe. A single switch statement handles only 4 unhappy paths:

switch(r.tag) {
  case 'Timeout': ...
  case 'BadStatus': ...
  case 'NetworkErr': ...
  case 'BadBody': ...Code language: PHP (php)

BUT you still have the option to retry, convert to another error type, or recover, all in a type-safe way.

Our Product Owner wants a new Domain Error if account is not activated:

type NotActivated = { tag: 'NotActivated' }

if(!account.activated) {
  return err({ tag: 'NotActivated' })Code language: JavaScript (javascript)

Even in happy path, we can have errors; we model them intentionally in our Domain, the compiler ensures we handle them as our code grows.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *