# Errors

{% hint style="info" %}
Example code: [src/lib/errors](https://github.com/jbreckmckye/node-typescript-architecture/tree/master/src/lib/errors)
{% endhint %}

But what if something goes _wrong_?

In the case of our example code, simulating a library of books, loaning a book can throw all kinds of exceptions:

* the customer might not yet have a library card
* the book being scanned might not belong to the library
* the customer might already have reached their loan limit
* the customer might have outstanding fines
* et cetera

When this happens, what we'd like is for our application to intercept the error and respond appropriately to the user, e.g. with a human readable error message.

To do this we need a set of Error classes.

### Which errors should I define in my lib?

Only errors that are to do with the domain itself - either invalid operations or entities in invalid state. Errors thrown by things in context should be defined in the adapter layer.

The point is that your `lib/adapters` module represents all the errors that can be thrown by the _library._&#x20;

### Extending JavaScript errors

In JavaScript you can extend the native `Error` class to include additional properties and customise the message property. This can help you pass useful information to the user and any error reporting system you have in place.

Let's look at a custom error in our Library project:

```typescript
export class UserLoanLimitExceeded extends Error {
  public invalidOperationErr = true

  constructor(public userId: UUID) {
    super(`User with ID ${userId} has exceeded their loan limit`)
  }
}
```

You'll notice several things:

Firstly, the **invalidOperationErr** property is there so that downsteam code knows it can report the error to the user, who has just attempted something impossible. You always want to **opt in to making errors public**, as unfiltered errors can reveal a lot of information to malicious users.

Secondly, the **userId** property is there so that any logging or reporting can capture it.

And finally, there's a custom message, formatted with the user's ID. This can be used as a more human readable error message.

{% hint style="info" %}
What fields and customisations you make are entirely up to you. None are mandated by this architecture. The important thing is to think carefully about what reporting you need and how exceptions should be presented to the user. Once you know that, the question of which fields you need will answer itself.
{% endhint %}

### Using assertions (and errors) to narrow typescript types

One new feature as of TypeScript 3.7 is the `asserts`keyword. This lets you tag a function as asserting than an input has a particular type.

This is really helpful when a context function can return a type or null, but to be valid it really has to be defined. For instance:

```typescript
export async function loanBook (ctx: Context, loanInput: LoanInput): Promise<Loan> {
  ...

  const user = await userStore.find(loanInput.userId)
  assertUser(user, loanInput.userId)

  ...
}

function assertUser (user: User | null, id: UUID): asserts user is User {
  if (!user) throw new UserDoesNotExist(id)
}
```

In this example, the user store can return a user or null. But we don't want to log a loan by a 'null' user. By calling `assertUser` we can both throw an error in this case, and tell TypeScript that they type of `user` has gone from `User | null` to just `User`. This means you won't have to do any annoying casts or `!` operators.

{% hint style="info" %}
Assertions let you transform a (validValue | invalidValue) -> (validValue | throw err)
{% endhint %}

### An aside: assertions beyond null checks

Imagine you have a system for managing magazine subscriptions, with an operation to `upgradeSubscription`. How do you handle someone upgrading a subscription that's been cancelled?

One way is to encode two types:

```typescript
type Subscription =
  | ActiveSubscription
  | CancelledSubscription
  
type ActiveSubscription = {
  tag: 'activeSubscription',
  paymentMethod: UUID,
  ...
}

type CancelledSubscription = {
  tag: 'cancelledSubscription',
  paymentMethod: null,
  ...
}

function getSubscription () : Subscription {...}
```

And write an assertion:

```typescript
function assertActive (sub: Subscription): asserts sub is ActiveSubscription {
  if (sub.tag !== 'activeSubscription') throw new SubscriptionNotActive(sub)
}
```

Now we can use our assertion and type system to make illegal upgrades impossible to represent without causing a TypeScript error:

```typescript
function upgrade (ctx: Ctx, sub: Subscription) {
  assertActive(sub)
  
  // If charge() won't accept null,
  // TypeScript will complain if we don't assertActive
  charge(sub.paymentMethod)
}
```

{% hint style="info" %}
Using types to express not just that a given data structure is correct, but that its _semantics_ are correct, is called using **invariants**.
{% endhint %}

### Putting it together: a practical example

Now we can use all these ideas to write the full 'loan book' operation:

```typescript
export async function loanBook (ctx: Context, loanInput: LoanInput): Promise<Loan> {
  const {
    backend: { userStore, bookStore, loanStore },
    events
  } = ctx

  const user = await userStore.find(loanInput.userId)
  assertUser(user, loanInput.userId)

  const book = await bookStore.find(loanInput.bookId)
  assertBook(book, loanInput.bookId)

  const existingLoan = await loanStore.getLoan(book)
  if (existingLoan) {
    throw new BookAlreadyLoaned(loanInput.bookId)
  }

  const userLoans = await loanStore.getUserLoans(user)
  if (userLoans.length > 3) {
    throw new UserLoanLimitExceeded(loanInput.userId)
  }

  const loan = await loanStore.takeLoan({
    bookId: book.id,
    userId: user.id
  })

  await events.onLoanMade({
    bookName: book.name
  })

  return loan
}

function assertUser (user: User | null, id: UUID): asserts user is User {
  if (!user) throw new UserDoesNotExist(id)
}

function assertBook (book: Book | null, id: UUID): asserts book is Book {
  if (!book) throw new BookDoesNotExist(id)
}
```

* The function now checks the operation is valid, and throws typed, reportable errors if the user attempts to make a nonsensical loan.
* Assertion functions mean that we can e.g. get the `book.id` without TypeScript complaining that the book could be `null`.
* We can use the `invalidOperationErr` property to tell whether an error is coming from the operation (reportable) or the context (not to be reported).
