# 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 a write whose outcome is unknown (see below). The database names each error it reports with a code, such as `https://docs.eventsourcingdb.io/errors/precondition-violated`, and it is the code that decides:

- If a precondition did not hold (`precondition-violated`), the error belongs to `ErrConflict`.
- If the database refuses the request for now, since it is shutting down (`server-shutting-down`), its store is read-only (`store-read-only`), it rebuilds its indexes (`indexes-rebuilding`), it restores a backup (`restore-in-progress`), its disk is full (`disk-full`), its store has failed and waits for a restart (`store-failed`), or its license has run out (`license-invalid`) or its store has reached the capacity of the license (`capacity-exceeded`), the error belongs to `ErrTransient`. Trying again only helps once somebody has dealt with the cause in some of these cases, but reading the state anew never does, so none of them is a conflict.
- Every other code means that the request itself is wrong, for example a malformed subject, an event that does not match the schema of its type, or a range of `FromLatestEvent` that no event can satisfy, since the latest event of the type comes after the upper bound, and the error belongs to `ErrPermanent` (see **[Reading Events](/docs/architecturekit/v2.0/reading-events)**). So does a code that the kit does not know, from a newer database.

*(Added in 2.0; Changed in 2.1)* A failure without a code is sorted by what happened:

- If the database can not be reached, if the connection breaks or stalls, if the request did not arrive in time (`408`), if the database asks to slow down (`429`), or if it is unable to answer for now (`5xx`), the error belongs to `ErrTransient`. So does an error within a stream that a database that only offers the v1 API reports without a code, which it only does when reading is disturbed, for example because it is shutting down.
- 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 too large (`413`), the error belongs to `ErrPermanent`. For a rejected API token, the message says so.

*Note that a database that only offers the v1 API, such as EventSourcingDB 1.2, reports its errors without a code. The client gives those that clearly match an error of the v2 API its code, and the kit sorts the others by their status, as it did in version 1 of the kit.*

```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; Changed in 2.1)* A failure of the database wraps the error of the client after its category. So `errors.As` finds the `*eventsourcingdb.DBAPIError` with the status, the code, and the description the database gave, for example to log them, and `errors.Is` finds `eventsourcingdb.ErrInvalidServerHeader` and `eventsourcingdb.ErrConnectionStalled`. 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/v2.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/v2.0/mapping-errors-to-status-codes)** and **[Getting a Single Item](/docs/architecturekit/v2.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. What limits the wait for its answer is the stall timeout of the client, which ends the request once the database stops answering (see **[Configuring the Stall Timeout](/docs/eventsourcingdb/sdks/go#configuring-the-stall-timeout)**).

*(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, which the client decides and reports with `eventsourcingdb.ErrOutcomeUnknown`. The client had connected, but no answer arrived that says that nothing was stored, for example because the connection broke, the answer was cut off or could not be decoded, or the database stalled. 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": outcome unknown, the events may have been written nevertheless: …`, 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/v2.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; Changed in 2.1)* Only a write that certainly stored nothing keeps its category: one for which the client could not connect to the database at all, and one that the database refused, with a code, such as `precondition-violated`, or with a status that it only answers before writing anything. That way, an error of the category `ErrTransient` means for a write as well that trying it again unchanged is safe. The client applies the same rule in every SDK of EventSourcingDB, which errs on the safe side: a request that was cut off after connecting, but before all of it was sent, leaves the outcome open as well. 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/v2.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/v2.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/v2.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/v2.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/v2.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/v2.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 the connection stalled, or because connecting to the database timed out, both of which are reported as `context.DeadlineExceeded` as well, the failure of the database still lets `errors.Is` and `errors.As` reach that error, such as `eventsourcingdb.ErrConnectionStalled`, but not as `context.Canceled` or `context.DeadlineExceeded`, so that it does not look like the end of the context. The same goes for `ErrOutcomeUnknown`, for example after a write stalled while it 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/v2.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/v2.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).*
