# Composing Subjects

So far, subjects have been composed by hand.

To define their structure once, create a `SubjectScheme` with a pattern, and use placeholders in braces for the variable parts. Parsing the pattern once is enough, so keep the scheme in a `static`:

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

use architecturekit::SubjectScheme;

static BOOKS: LazyLock<SubjectScheme> = LazyLock::new(|| SubjectScheme::new("/books/{book_id}"));
```

Each segment of a subject may only contain the characters that EventSourcingDB allows: the letters `A-Z` and `a-z`, the digits `0-9`, underscores, and hyphens (see **[Subjects](/docs/eventsourcingdb/subjects)**). This applies to the literal segments of the pattern as well as to the values that fill its placeholders. The names of the placeholders follow the same rule, as `book_id` and `license-agreement` do, so that a typo, such as a brace too many in `{book_id}}`, is not taken for a name.

To compose a subject, call the `build` method with one value per placeholder, in the order in which they appear in the pattern. **It returns a `Subject`, which is what a command names as its subject**, so use it in every command that acts on a book:

```rust
let acquire_command = AcquireBook {
  book: BOOKS.build(&["42"]),
  title: "2001 – A Space Odyssey".to_owned(),
  author: "Arthur C. Clarke".to_owned(),
  isbn: "978-0756906788".to_owned(),
};

let borrow_command = BorrowBook {
  book: BOOKS.build(&["42"]),
  reader_id: "23".to_owned(),
  borrowed_until: "2026-10-24".to_owned(),
};

let return_command = ReturnBook { book: BOOKS.build(&["42"]) };
```

To take a subject apart, call the `match_subject` method. It returns the values by placeholder name, and `None` if the subject does not follow the pattern or has a value that `build` would refuse. It is called `match_subject` rather than `match`, which is a keyword in Rust:

```rust
let values = BOOKS.match_subject("/books/42");
// values == Some(HashMap::from([("book_id", "42")]))
```

To take a single value out of a subject, call the `value` method with the subject and the name of the placeholder. Like `match_subject`, it returns `None` if the subject does not follow the pattern. Both take a `Subject` as well as text:

```rust
let book_id = BOOKS.value("/books/42", "book_id");
// book_id == Some("42")
```

*Note that `value` panics for a placeholder that the pattern does not have, such as one with a typo, whatever the subject is. The map that `match_subject` returns simply has no entry for such a name instead, so use `value` for a single value, and `match_subject` for several values of the same subject.*

To get the pattern and the names of the placeholders, call the `pattern` and the `placeholders` method respectively.

To read or observe the events of all books, for example in a projection, start from the subject that all of them lie under. Call the `root` method to get it from the scheme, rather than writing it down a second time. It returns the literal segments before the first placeholder, here `/books`, or `/` if the pattern starts with a placeholder:

```rust
let run = store.start_projection(
  Subjects::tree(BOOKS.root()),
  catalog_projection,
  token.clone(),
  ProjectionOptions::default(),
);
```

*Note that other subjects may lie under the same root, such as `/books/42/reviews/7` under `/books`. Use `match_subject` or `value` in the projection to tell them apart.*

*Note that a malformed pattern panics, including one with a literal segment or a placeholder name that contains a character EventSourcingDB does not allow, as does calling `build` with the wrong number of values, with an empty value, or with a value that contains such a character, for example a slash, a dot, or a space.*

Values that come from outside, such as an ID in a request, may well be empty or contain such characters, and that is not a programming error. So **always check them before building a subject**: call the `check` method with the same values as `build`. It returns a `SubjectValuesError` that says what is wrong, such as which characters a value may contain, instead of panicking. To check the values and compose the subject in one step, call the `try_build` method, which returns the subject or the same error. The error may reach the caller of an API, where `?` answers it with `400 Bad Request` (see **[Handling Commands over HTTP](/docs/architecturekit/rust/handling-commands-over-http)**), so it names the placeholder of the value, but neither the crate nor the pattern, as in `value for "book_id" must not be empty`:

```rust
BOOKS.check(&[&book_id])?;
```
