# Using Preconditions

Every command declares at least one precondition, so that writing without any check is always a decision, never an oversight.

There are these kinds, each made by a function of `Precondition`:

- `on_state_read` guards the state the command is decided on.
- `on_pristine_subject`, `on_populated_subject`, and `on_event_id` require the subject to have no events yet, to have events, or to be on a given event, for example the revision the caller hands over.
- `require` turns any other precondition of the client SDK into one of the command, such as an EventQL query.
- `unconditionally` writes without any check.

Preconditions can be combined, and all of them must hold. If a precondition does not hold, nothing is written, and `execute` fails with a conflict, `Transient::Conflict`, which belongs to the category `Category::Transient` (see **[Handling Errors](/docs/architecturekit/rust/handling-errors)**). If a command declares no preconditions, or combines `unconditionally` with others, `execute` fails with `ExecuteError::Permanent` before reading anything. To check the preconditions of a command this way without executing it, call the `check_preconditions` function with the command, which returns the same failure as a `PreconditionsError`, or `Ok(())`. The `check_read_options` function does the same for the options of a read (see **[Reading Events](/docs/architecturekit/rust/reading-events)**).

To find out what kind a precondition is, call its `is_on_state_read` or `is_unconditional` function. Its `database` function returns the precondition of the client SDK that the database checks for it: the one `require` made it from, which is how `on_pristine_subject` and `on_populated_subject` make theirs, or the one of `is_subject_on_event_id` for `on_event_id`. For any other, it returns `None`.

`on_pristine_subject`, `on_populated_subject`, and `on_event_id` check the subject alone. To check it together with every subject below it, take the function of the client SDK that does the same, such as `is_subject_pristine`, hand it `PreconditionOptions` with `recursive` set to `true`, and wrap it with `require`. For example, to acquire a book only if neither its subject nor any subject below it has events yet:

```rust
use architecturekit::eventsourcingdb;

impl Command for AcquireBook {
  fn subject(&self) -> Subject {
    self.book.clone()
  }

  fn preconditions(&self) -> Vec<Precondition> {
    vec![Precondition::require(eventsourcingdb::Precondition::is_subject_pristine(
      self.subject(),
      Some(eventsourcingdb::PreconditionOptions { recursive: true }),
    ))]
  }
}
```

*Note that only a database that offers the v2 API checks a recursive precondition. Against a database that offers just the v1 API, such as EventSourcingDB 1.2, the client does not send the write at all, so that nothing is written, and `execute` and `write` fail with a permanent failure, since only upgrading the database helps (see **[Handling Errors](/docs/architecturekit/rust/handling-errors)**).*

## Guarding Against Concurrent Changes

If a command may only write events in case nothing has been written to its subject since `execute` read the state, use `Precondition::on_state_read`. This fits most commands, since the decider decides on exactly that state:

```rust
impl Command for ReturnBook {
  fn subject(&self) -> Subject {
    self.book.clone()
  }

  fn preconditions(&self) -> Vec<Precondition> {
    vec![Precondition::on_state_read()]
  }
}
```

`execute` fills in the ID of the last event it has read. For a subject without any events, it requires the subject to still be pristine instead. This also holds with several processes writing to the same subjects, and with a state cache (see **[Caching States](/docs/architecturekit/rust/caching-states)**).

If something else was written in between, the command fails with a conflict. Since the decider decided on the state that `execute` read, deciding again on the new state is what would have happened had the command arrived a moment later. To have `execute` do so, call `conflict_retries` on the builder of the store, with the number of times to decide again:

```rust
let store =
  Store::builder(client, "https://library.eventsourcingdb.io").conflict_retries(4).build();
```

`execute` then reads the state anew, and the decider decides on it, up to four more times. Once the retries are used up, the command fails with the conflict, as without them.

*Note that the store decides again only on a conflict. A write whose outcome is unknown, `ExecuteError::OutcomeUnknown`, is never tried again, since its events may have been stored (see **[Handling Errors](/docs/architecturekit/rust/handling-errors)**).*

*Note that this only applies to commands whose preconditions include `on_state_read`. A command that checks only a revision the caller hands over is never decided again, since the caller has to learn about the conflict, and deciding again would fail the same way (see **[Checking the Revision of the Caller](/docs/architecturekit/rust/using-preconditions#checking-the-revision-of-the-caller)**). A command that checks both, or `on_state_read` together with any other precondition, such as one made with `require`, is decided again only if `on_state_read` did not hold. A database that offers the v2 API names the precondition that did not hold, so if it is another one, such as an outdated revision of the caller, `execute` reports the conflict at once. A database that offers just the v1 API, such as EventSourcingDB 1.2, does not name it, so then `execute` decides again whenever `on_state_read` is among the preconditions, and if another one did not hold, every attempt fails the same way, until the retries are used up. On the same subject, a revision of the caller and `on_state_read` need not both be checked: if the revision of the caller holds, so does `on_state_read`.*

*Note that the conflict is a `Transient::Conflict` in every case, and its source is the error of the client SDK, which holds the answer of the database (see **[Handling Errors](/docs/architecturekit/rust/handling-errors)**). Through the v2 API, its details name the precondition that did not hold by its index among the preconditions the command declares, as in `{"precondition":1}`, so that you can find out yourself which one it was.*

## Checking the Revision of the Caller

If a command may only write events in case its subject has not changed since the caller last read it, for example in a user interface, use `Precondition::on_event_id` with the subject and the ID of the last event the caller has seen. For that, add a field for the ID, which is a `Revision`:

```rust
use architecturekit::Revision;

#[derive(Clone)]
struct BorrowBook {
  book: Subject,
  reader_id: String,
  borrowed_until: String,
  expected_event_id: Revision,
}

impl Command for BorrowBook {
  fn subject(&self) -> Subject {
    self.book.clone()
  }

  fn preconditions(&self) -> Vec<Precondition> {
    vec![Precondition::on_event_id(self.subject(), self.expected_event_id)]
  }
}
```

*Note that the caller has to provide the event ID. A view can keep it for that purpose, as long as its items follow every event of their subject (see **[Defining Views](/docs/architecturekit/rust/defining-views)**).*

*Note that an outdated event ID is reported as a conflict at once, also by a store that decides again on conflicts, since deciding again would fail the same way. That holds as well for a command that declares `on_state_read` besides, as long as the database offers the v2 API, which names the precondition that did not hold. A database that offers just the v1 API does not, so then `execute` decides again until the retries are used up (see **[Guarding Against Concurrent Changes](/docs/architecturekit/rust/using-preconditions#guarding-against-concurrent-changes)**).*

*Note that the event ID usually comes from the caller, so it is read as a `Revision` before the command exists, with `parse`, which refuses an empty ID, or one that is not an event ID at all, with a `NotARevision` that names it, as in `not a revision: "abc"`. serde reads a `Revision` from a string the same way, so a request body with such an ID can not be read. The `httpapi` module answers a `NotARevision` with `400 Bad Request` and the error as the message (see **[Mapping Errors to Status Codes](/docs/architecturekit/rust/mapping-errors-to-status-codes)**). `require` with `is_subject_on_event_id` of the client SDK takes the ID as a string instead, and leaves it to the database, which refuses such an ID as a malformed request, so that `execute` fails with a permanent failure.*

## Preventing Duplicates

If a command may only write events in case its subject does not yet have any events, use `Precondition::on_pristine_subject`:

```rust
impl Command for AcquireBook {
  fn subject(&self) -> Subject {
    self.book.clone()
  }

  fn preconditions(&self) -> Vec<Precondition> {
    vec![Precondition::on_pristine_subject(self.subject())]
  }
}
```

## Requiring an Existing Subject *(Requires EventSourcingDB 1.2)*

If a command may only write events in case its subject already has at least one event, use `Precondition::on_populated_subject`. Combine it with `on_state_read` to also guard the state:

```rust
impl Command for ReturnBook {
  fn subject(&self) -> Subject {
    self.book.clone()
  }

  fn preconditions(&self) -> Vec<Precondition> {
    vec![Precondition::on_populated_subject(self.subject()), Precondition::on_state_read()]
  }
}
```

*Note that a database without the `isSubjectPopulated` precondition refuses the write with `400 Bad Request`, so that `execute` and `write` fail with a permanent failure.*

## Enforcing Rules Across Subjects

If a command may only write events depending on an EventQL query, use the `is_eventql_query_true` function of the client SDK, and wrap it with `Precondition::require`. For example, to acquire every ISBN only once, extend the preconditions of `AcquireBook`:

```rust
impl Command for AcquireBook {
  fn subject(&self) -> Subject {
    self.book.clone()
  }

  fn preconditions(&self) -> Vec<Precondition> {
    vec![
      Precondition::on_pristine_subject(self.subject()),
      Precondition::require(eventsourcingdb::Precondition::is_eventql_query_true(format!(
        "FROM e IN events WHERE e.type == 'io.eventsourcingdb.library.book-acquired' AND e.data.isbn == '{}' PROJECT INTO COUNT() == 0",
        self.isbn,
      ))),
    ]
  }
}
```

*Note that the query must return a single row with a single value, which is interpreted as a boolean.*

The ISBN comes from outside, but becomes part of the query as text, so check it first. `execute` sends the query to the database only together with the events, once the decider has decided, so a check in the decider keeps every value that is not an ISBN out of the query. Here, an ISBN consists of digits and hyphens, and may end with an `X`. A regular expression checks that, with the `regex` crate, which you add with `cargo add regex`:

```rust
use std::sync::LazyLock;

use architecturekit::{Decider, DomainError};
use regex::Regex;

static ISBN: LazyLock<Regex> = LazyLock::new(|| Regex::new("^[0-9][0-9-]*[0-9X]$").unwrap());

fn acquire_book() -> Decider<AcquireBook, Book, DomainError> {
  Decider::new(|command: AcquireBook, book: Book| async move {
    if !ISBN.is_match(&command.isbn) {
      return Err(DomainError::new(format!("{:?} is not an ISBN", command.isbn)));
    }

    // ...
  })
}
```

*Note that a value must never go into an EventQL query unchecked. A quote, as in `it's`, breaks the query, so that the write fails, and a value made up for that purpose changes what the query checks.*

When the ISBN is taken, the database refuses the write with the code `precondition-violated`, the same answer as for a subject that has changed since it was read. Which precondition did not hold is only in the details of the answer, so the kit reports the two alike:

- The command fails with a `Transient::Conflict`, which is transient (see **[Handling Errors](/docs/architecturekit/rust/handling-errors)**).
- The `httpapi` module answers it with `409 Conflict` and the message `conflict: the data has changed since it was read`, which tells the caller to try again, although that never helps (see **[Handling Commands over HTTP](/docs/architecturekit/rust/handling-commands-over-http)**).
- If the command declares `on_state_read` as well, and the store decides again on conflicts, `execute` reads the details to tell the two apart, and reports the taken ISBN at once, since deciding again would fail the same way. A database that offers just the v1 API sends no details, so then `execute` decides again and again in vain, until the retries are used up (see **[Guarding Against Concurrent Changes](/docs/architecturekit/rust/using-preconditions#guarding-against-concurrent-changes)**).

To answer a taken ISBN with a domain error instead, check it in the decider, against a view that holds the acquired books by their ISBN (see **[Defining Views](/docs/architecturekit/rust/defining-views)**). The catalog does not hold the ISBN, so here, a small view of its own does, which a projection fills (see **[Defining Projections](/docs/architecturekit/rust/projections#defining-projections)**):

```rust
use std::sync::Arc;

use architecturekit::{Envelope, InMemoryView, Projection, ViewError};

#[derive(Clone)]
struct IsbnItem {
  isbn: String,
  book_id: String,
}

struct IsbnProjection {
  isbns: Arc<InMemoryView<String, IsbnItem>>,
}

impl Projection for IsbnProjection {
  type Event = BookEvent;
  type Error = ViewError;

  async fn apply(&self, event: Envelope<BookEvent>) -> Result<(), ViewError> {
    let BookEvent::Acquired(acquired) = &event.data else {
      return Ok(());
    };
    let Some(book_id) = BOOKS.value(&event.subject, "book_id") else {
      return Ok(());
    };

    self
      .isbns
      .insert(&event, IsbnItem { isbn: acquired.isbn.clone(), book_id: book_id.to_owned() })?;

    Ok(())
  }
}
```

The decider gets the view from a function that creates it, as it gets a clock (see **[Using the Current Time](/docs/architecturekit/rust/using-the-current-time)**). The future of a decider owns what it uses, so the closure hands every decision a clone of the `Arc`. If the ISBN is taken, the decider returns a domain error, which the `httpapi` module answers with `422 Unprocessable Entity` and the error as the message, as in `ISBN has already been acquired: 978-0756906788`:

```rust
use architecturekit::KeyedView;

fn acquire_book(
  isbns: Arc<InMemoryView<String, IsbnItem>>,
) -> Decider<AcquireBook, Book, DomainError> {
  Decider::new(move |command: AcquireBook, book: Book| {
    let isbns = Arc::clone(&isbns);

    async move {
      if !ISBN.is_match(&command.isbn) {
        return Err(DomainError::new(format!("{:?} is not an ISBN", command.isbn)));
      }

      let Ok(taken) = isbns.get(&command.isbn).await;
      if taken.is_some() {
        return Err(DomainError::new(format!("ISBN has already been acquired: {}", command.isbn)));
      }

      // ...
    }
  })
}

let isbns = Arc::new(InMemoryView::new(|item: &IsbnItem| item.isbn.clone()));
let isbn_projection = IsbnProjection { isbns: Arc::clone(&isbns) };
let acquire_book = acquire_book(isbns);
```

Keep the EventQL precondition all the same, as the guard. A view lags behind the events, so when two acquisitions of the same ISBN arrive at the same moment, both pass the view, since it has seen neither of them. The database writes only one of them, and the other fails with a conflict, which is the right answer in that rare case: once the view has seen the first acquisition, trying again gets the domain error.

## Writing Unconditionally

If a command may write its events whatever has been written to its subject in the meantime, for example `CommentOnBook`, which only records a comment that does not depend on the state, use `Precondition::unconditionally`:

```rust
impl Command for CommentOnBook {
  fn subject(&self) -> Subject {
    self.book.clone()
  }

  fn preconditions(&self) -> Vec<Precondition> {
    vec![Precondition::unconditionally()]
  }
}
```

*Note that `unconditionally` can not be combined with other preconditions.*
