# Handling Errors

Apart from the end of the context, `ErrOutcomeUnknown`, `ErrNotCaughtUp`, `ErrNotARevision`, and `ErrEmptyRange`, which belong to no category (see below), every failure of ArchitectureKit itself in reading and writing belongs to one of four categories.

Use `errors.Is` to check for a category rather than for a concrete error:

- `ErrDomain` means that a business rule rejected the command, as with `NewDomainError`.
- `ErrConflict` means that a precondition did not hold.
- `ErrTransient` means that trying again may help, for example if the database can not be reached. A write that fails with it has certainly stored nothing, so trying it again can not store its events twice.
- *(Changed in 2.1)* `ErrPermanent` means that trying again will not help, for example if an event could not be decoded, if the data of an event can not be encoded as JSON, such as a float `NaN`, if an event does not match the schema of its type, if a subject contains an event type the state has no rule for, if a decider returns one, or if it returns an event that is `nil`.

*(Changed in 2.0)* A failure of the database is sorted by what its answer means, the same way for reading and for writing, apart from `409`, and apart from a write whose outcome is unknown (see below):

- If the database can not be reached, if the connection breaks, if the database asks to slow down (`429`), or if it is unable to answer for now (`5xx`), for example because it is shutting down, the error belongs to `ErrTransient`.
- If the answer does not come from an EventSourcingDB, the error belongs to `ErrTransient` as well, since a proxy in front of the database answers on its own while the database restarts. The message says so, so that a wrong address stands out in the log.
- If the database rejects the API token (`401`), or if it rejects the request itself, for example because it is malformed (`400`) or too large (`413`), the error belongs to `ErrPermanent`. For a rejected API token, the message says so.
- If a precondition did not hold when writing (`409`), the error belongs to `ErrConflict`. If an event does not match its schema, which the database answers with the same status, it belongs to `ErrPermanent`.
- Reading has no preconditions, so when reading, a `409` is a refusal of the request like any other, and the error belongs to `ErrPermanent`. The database answers a read with `409` if the latest event of the type given to `FromLatestEvent` comes after the upper bound, and trying again never helps, since new events only move the latest one further away (see **[Reading Events](/docs/architecturekit/v1.0/reading-events)**).

```go
writtenEvents, err := architecturekit.Execute(
  // ...
)

switch {
case errors.Is(err, architecturekit.ErrDomain):
  // A business rule rejected the command.
case errors.Is(err, architecturekit.ErrConflict):
  // A precondition did not hold.
case errors.Is(err, architecturekit.ErrOutcomeUnknown):
  // The events may have been stored, so find out before trying again.
case errors.Is(err, architecturekit.ErrTransient):
  // Trying again may help.
case errors.Is(err, architecturekit.ErrPermanent):
  // Trying again will not help.
}
```

*Note that `ErrConflict` is a special case of `ErrTransient`, so check for it first.*

*(Changed in 2.0)* A failure of the database wraps the error of the client after its category. So `errors.As` finds the `*eventsourcingdb.DBAPIError` with the status code and the reason the database gave, for example to log them, and `errors.Is` finds `eventsourcingdb.ErrInvalidServerHeader` and `eventsourcingdb.ErrHeartbeatTimeout`. It is the category, though, that tells what the failure means, so check for it first.

An error that your own code returns, for example from a decider or a projection, passes through unchanged, so it belongs to a category only if you wrap it with one, as `NewDomainError` does. A panic in a projection, on the other hand, comes back as an error of the category `ErrPermanent` (see **[Running Projections](/docs/architecturekit/v1.0/projections#running-projections)**). The `httpapi` and `query` packages have errors of their own, which `StatusFor` maps to status codes (see **[Mapping Errors to Status Codes](/docs/architecturekit/v1.0/mapping-errors-to-status-codes)** and **[Getting a Single Item](/docs/architecturekit/v1.0/defining-queries#getting-a-single-item)**).

*(Changed in 2.0)* If the context ends, reading stops, and so does writing, but only before it has begun. The error then wraps the one of the context, `context.Canceled` or `context.DeadlineExceeded`, which belongs to no category, as in `architecturekit: reading "/books/42": context canceled`. Check for it with `errors.Is` as well. This is never a partial success: a read that the context cut short fails rather than handing out part of a state, and `Execute` writes nothing once the context has ended, also if it ends while the decider decides. A write that has begun, though, is finished, since the database may store the events as soon as the request has left, and `Execute` and `Write` return its result, the events written or the failure of the write, rather than the error of the context.

*(Changed in 2.0)* Another exception is `ErrOutcomeUnknown`, which belongs to no category either. It means that a write failed in a way that leaves open whether the database stored the events. The request had left, but no complete answer arrived, for example because the connection broke, the answer was cut off or could not be decoded, or a timeout of the client ran out. Or the answer was one that may come after the events were stored, such as `500`, or one that does not come from an EventSourcingDB, such as a `502` or `504` of a proxy in front of it. The error wraps `ErrOutcomeUnknown` and the error of the client, as in `outcome unknown: writing "/books/42": Post "http://localhost:3000/api/v1/write-events": …`, which ends in what went wrong, such as `connection reset by peer`, so check for it with `errors.Is`, and use `errors.As` to get at the answer of the database, as for a category. `StatusFor` maps it to `500 Internal Server Error` (see **[Mapping Errors to Status Codes](/docs/architecturekit/v1.0/mapping-errors-to-status-codes)**).

It is not `ErrTransient`, since trying again may store the events twice: with `Unconditionally`, the same events are written once more, and with `OnStateRead`, `Execute` reads the state anew, which then holds the events, so the precondition holds, and the decider decides once more. So do not try again blindly. Find out first whether the events were stored, for example by reading the subject, or try again only with a precondition that refuses the same events a second time, such as `OnEventID` with the revision the command was decided on, or `OnPristineSubject` for a subject that the command creates. Neither `Execute` nor `Write` tries again by itself, not even on a store that decides again on conflicts.

*(Changed in 2.0)* Only a write that certainly stored nothing keeps its category: one whose request never left completely, as when the database can not be reached, and one that the database refused before writing anything, with a `4xx`, such as `409` for a precondition that did not hold, with `503`, which it answers while it shuts down or does not permit writing, or with `507`, which it answers once the license has run out. That way, an error of the category `ErrTransient` means for a write as well that trying it again unchanged is safe. Reading writes nothing, so it is not affected: there, every failure of the database belongs to a category, as described above.

Yet another exception is `ErrNotCaughtUp`, which belongs to no category either. It means that a view did not catch up with written events, which is what every error of `WaitForWritten` wraps, whether the time it waits has run out, the context has ended, or the view could not be asked (see **[Waiting for Written Events](/docs/architecturekit/v1.0/reading-your-own-writes#waiting-for-written-events)**). The events were written, so the write has succeeded, and only the view lags behind. So it is not `ErrTransient`, since trying the write again would store its events twice, and not `ErrPermanent`, since the view may still catch up. The error wraps `ErrNotCaughtUp` and says why, as in `not caught up: the events were written, but the view did not catch up within 5s`, so check for it with `errors.Is`, before any category, which the cause it keeps may belong to. `StatusFor` maps it to `500 Internal Server Error` (see **[Mapping Errors to Status Codes](/docs/architecturekit/v1.0/mapping-errors-to-status-codes)**), but a handler that waits after a command usually answers it with success, since the write has succeeded.

One more exception is `ErrNotARevision`, which belongs to no category either. It means that a value that was handed over is not a revision, such as a bound of `Read`, the event ID of `OnEventID`, the revision for `WaitFor`, or a value for `CompareRevisions` or `ParseRevision` (see **[Comparing Revisions](/docs/architecturekit/v1.0/reading-your-own-writes#comparing-revisions)**). The error wraps `ErrNotARevision` and names the value, so check for it with `errors.Is` as well. Such a value usually comes from a request, so `StatusFor` maps it to `400 Bad Request` (see **[Mapping Errors to Status Codes](/docs/architecturekit/v1.0/mapping-errors-to-status-codes)**). Only a function of a view, which refuses the ID of an event it is to apply, wraps it together with `ErrPermanent` (see **[Defining Views](/docs/architecturekit/v1.0/defining-views)**).

The last exception is `ErrEmptyRange`, which belongs to no category either. It means that the bounds of `Read` leave no room for any event, such as `BeforeEvent("0")` (see **[Reading Events](/docs/architecturekit/v1.0/reading-events)**). The error wraps `ErrEmptyRange` and names the values, so check for it with `errors.Is` as well. Such bounds usually come from a request, so `StatusFor` maps it to `400 Bad Request` as well.

*(Changed in 2.0)* *Note that only the end of the context matches `context.Canceled` or `context.DeadlineExceeded`. If an error of the client matches one of them all the same, for example because connecting to the database timed out, which the standard library reports as `context.DeadlineExceeded`, the failure of the database keeps that error in its message only, so that it does not look like the end of the context. The same goes for `ErrOutcomeUnknown`, for example after a timeout of the client while a write waited for its answer.*

*Note that `ErrUnverified` is a special case of `ErrPermanent`, which means that an event failed its verification (see **[Verifying Events](/docs/architecturekit/v1.0/verifying-events)**). Since that may point to a security incident rather than a mistake, check for it before `ErrPermanent` if you want to treat it differently, for example to raise an alarm.*

*Note that `Execute` does not try again by itself, unless the store decides again on conflicts (see **[Guarding Against Concurrent Changes](/docs/architecturekit/v1.0/using-preconditions#guarding-against-concurrent-changes)**). To try again after a transient failure, call `Execute` again, which is safe, since a write that failed with an error of the category `ErrTransient` has stored nothing. After `ErrOutcomeUnknown`, find out first whether the events were stored (see above).*
