# Describing Events with Schemas

Every event type has a JSON schema, which the database checks every event of the type against, once the schema is registered (see **[Registering Event Schemas](/docs/architecturekit/rust/registering-event-schemas)**).

The `Event` derive derives it from the struct, so that it describes exactly what serde writes for the event:

- A struct with named fields is an object with the fields serde writes: named as `rename` and `rename_all` say, and without the fields marked with `skip` or `skip_serializing`. Fields with `skip_serializing_if` are optional, all others are required, and no other fields are allowed. A newtype struct, and a struct marked with `transparent`, is described like the value it wraps.
- A `bool` is a boolean, an `i64` or any other integer type an integer, an `f64` or an `f32` a number, and a `String`, a `str`, or a `char` a string.
- A `Vec`, a `VecDeque`, a slice, a `HashSet`, and a `BTreeSet` is an array, and an array `[T; N]` an array of exactly `N` items. A `HashMap` and a `BTreeMap` is an object whose values all have the same schema. A `Vec<u8>` is an array of integers, since that is what serde writes for it.
- An `Option` may also be `null`, unless it is the type of an optional field, which serde leaves out instead of writing `null`. serde never writes a `Vec` or a `HashMap` as `null`, so a value that may be `null` is an `Option`, such as an `Option<Vec<String>>`. A `Box`, an `Rc`, an `Arc`, a `Cow`, and a reference are described like what they hold.
- A `DateTime` of the `chrono` crate is a string in the `date-time` format, a `serde_json::Number` a number, and a `serde_json::Value` allows any value.

For `BookBorrowed`, this yields the following schema:

```json
{
  "type": "object",
  "properties": {
    "borrowedBy": { "type": "string" },
    "borrowedUntil": { "type": "string" }
  },
  "required": ["borrowedBy", "borrowedUntil"],
  "additionalProperties": false
}
```

These rules do not change, since a registered schema can not change either.

*Note that EventSourcingDB currently reads every number in the data of an event as a 64-bit floating-point number, which holds an integer exactly only if its magnitude is at most 2^53 - 1, which is 9007199254740991. A larger integer is stored changed, without an error: 9007199254740993 comes back as 9007199254740992, and a time in nanoseconds, as `timestamp_nanos_opt` of chrono returns it, loses its last digits. Close to the largest `i64` or `u64`, the stored number may even lie beyond the range of the type, so that the event can not be read back any more, and `load`, `execute`, and a projection fail with a permanent failure. To keep a 64-bit integer exact, write it as a string, in a type of its own that serde writes as a string, and that describes itself as a string by implementing `Schema` by hand, as shown below.*

To constrain a value further than its Rust type does, declare a type for it, and implement `Schema` for it by hand, with a `schema` function that returns the JSON schema of the type, as a `Value` of the `serde_json` crate, which you add with `cargo add serde_json`. Wherever a field has that type, the derived schema takes it over. For example, `borrowedUntil` accepts any string so far. To make sure that it is a date, `BookBorrowed` could use a `Date` type for the field:

```rust
use architecturekit::{Event, Schema};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};

#[derive(Serialize, Deserialize)]
struct Date(String);

impl Schema for Date {
  fn schema() -> Value {
    json!({ "type": "string", "format": "date" })
  }
}

#[derive(Serialize, Deserialize, Event)]
#[event(type = "io.eventsourcingdb.library.book-borrowed")]
#[serde(rename_all = "camelCase")]
struct BookBorrowed {
  borrowed_by: String,
  borrowed_until: Date,
}
```

*Note that the other examples in this documentation keep `borrowed_until` a `String`. With the `Date` type, they would convert between the two, as in `Date(command.borrowed_until)`.*

The schema of an enum, a union, a unit struct, a tuple struct with several fields, a struct marked with `tag`, `content`, `into`, or `remote`, and a field marked with `flatten`, `serialize_with`, or `with` can not be derived, since serde writes them differently than their types describe. Deriving it fails to compile, and the message says why. Implement `Schema` by hand for such a type, as for `Date`, for example for an enum of the formats of a book, which serde writes as the names of its variants. For an event without data, write a struct with braces, as in `struct BookReturned {}`, rather than a unit struct, which serde writes as `null`. A field whose type implements `Schema` neither way fails to compile as well, with the message that the type does not describe its JSON schema. A recursive type compiles, but panics once its schema is derived, such as by `BookEvent::schemas()`, and names the type.

*Note that a field of type `serde_json::Value` allows any value, whatever it is meant to hold. To constrain such a field, give the event a schema of its own.*

If an event needs a schema that its fields can not express, name a function with `schema_with` in the `event` attribute. The function gets the derived schema as a `serde_json::Value`, and returns the one that is registered, so that it can start from the derived schema. For example, to require at least one of two optional fields:

```rust
use architecturekit::Event;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};

#[derive(Serialize, Deserialize, Event)]
#[event(type = "io.eventsourcingdb.library.book-corrected", schema_with = "at_least_one_field")]
struct BookCorrected {
  #[serde(skip_serializing_if = "Option::is_none")]
  title: Option<String>,
  #[serde(skip_serializing_if = "Option::is_none")]
  author: Option<String>,
}

fn at_least_one_field(mut derived: Value) -> Value {
  derived["minProperties"] = json!(1);
  derived
}
```

The function is named as a string, as serde names the functions it calls, and only once. A function that leaves the derived schema aside replaces it. An event whose schema can not be derived at all implements both `Event` and `Schema` by hand instead.

For a type within the data of an event, such as a nested struct, derive `Schema`, which follows the same rules. To say more about such a type than its fields can, name a function with `#[architecturekit(schema_with = "...")]` on the type, which works as `schema_with` does for an event. The attribute is named after the kit, so that it does not collide with the `schema` attribute of other derives, such as `ToSchema` of utoipa. The `Schema` derive reads `#[architecturekit(...)]` only on the type, as the `Event` derive reads `#[event(...)]`, and each refuses its attribute on a field. A field that needs a schema of its own gets a type of its own instead, such as `Date`.

*Note that a struct that writes two fields under the same name, for example because of `rename`, compiles, but panics once its schema is derived, and names the struct and the field.*

## Keeping Schemas in Files

An event's own schema does not have to be written in Rust. To keep it in a JSON file instead, embed the file with `include_str!`, and read it in the function that `schema_with` names, which leaves the derived schema aside:

```rust
use architecturekit::Event;
use serde::{Deserialize, Serialize};
use serde_json::Value;

#[derive(Serialize, Deserialize, Event)]
#[event(type = "io.eventsourcingdb.library.book-acquired", schema_with = "book_acquired_schema")]
struct BookAcquired {
  title: String,
  author: String,
  isbn: String,
}

fn book_acquired_schema(_derived: Value) -> Value {
  serde_json::from_str(include_str!("schemas/book-acquired.json"))
    .expect("schemas/book-acquired.json holds JSON")
}
```

`include_str!` embeds the file, relative to the source file that names it, when the application is compiled, so a missing file fails to compile. The function reads the JSON whenever the schema is derived, such as by `BookEvent::schemas()` when the application registers the schemas on start, so a file that does not contain valid JSON stops the application on start, before it writes anything.
