> Full SurrealDB documentation index: https://surrealdb.com/docs/llms.txt

# Working with types

The surrealdb-types crate provides the SurrealValue trait, the kind! macro and the value constructors used to move data between Rust and SurrealDB.

The [surrealdb-types](https://crates.io/crates/surrealdb-types) crate holds the public value type system shared across SurrealDB. It is kept separate from the database core so that types and type conversions can be used on their own, without pulling in the whole database, but it is also available from the main `surrealdb` crate under the `surrealdb::types` path.

## The `SurrealValue` trait

`SurrealValue` is the trait that converts a Rust type to and from a SurrealDB value. Deriving it is all that is needed to use a Rust type for serialisation and deserialisation.

The SDK does not generate Rust structs from a database schema. The struct for each table is written by hand and derives `SurrealValue`, and a field whose type does not match the stored value fails at deserialisation with an error that names the field. [`surrealkit typegen`](/docs/manage/schema-migration/typegen.md) describes a schema as JSON and generates TypeScript types, but not Rust. The opposite direction is available: `kind_of()` gives the SurrealQL type of a Rust type, as shown in [Getting the SurrealQL type of a Rust type](#getting-the-surrealql-type-of-a-rust-type).

To customise how a type is converted, such as renaming fields, flattening a nested struct into its parent or tagging enum variants, see [SurrealValue attributes](/docs/reference/rust/concepts/surrealvalue-attributes.md). The derive does not read `#[serde(...)]` attributes, so a field marked `#[serde(flatten)]` needs `#[surreal(flatten)]` instead. This also works for a generic field, as in `struct RecordData<V: SurrealValue> { id: RecordId, #[surreal(flatten)] data: V }`.

```rust
use surrealdb::engine::any::connect;
use surrealdb::types::SurrealValue;

#[derive(Debug, SurrealValue)]
struct Employee {
    name: String,
    active: bool,
}

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();

    db.use_ns("ns").use_db("db").await.unwrap();

    let mut res = db
        .query("CREATE employee:bobby SET name = 'Bobby', active = true")
        .await
        .unwrap();

    let bobby = res.take::<Option<Employee>>(0).unwrap().unwrap();

    // Employee { name: "Bobby", active: true }
    println!("{bobby:?}");
}
```

The `SurrealValue` trait can be implemented manually via three methods: one to indicate the matching SurrealDB type, a second to convert into a SurrealDB Value, and a third to convert out of a SurrealDB Value.

```rust
use surrealdb::engine::any::connect;
use surrealdb::types::{Datetime, Error, Kind, SurrealValue, Value};

#[derive(Debug)]
struct MyOwnDateTime(i64);

impl SurrealValue for MyOwnDateTime {
    fn kind_of() -> Kind {
        Kind::Datetime
    }

    fn into_value(self) -> Value {
        Value::Datetime(Datetime::from_timestamp(self.0, 0).unwrap())
    }

    fn from_value(value: Value) -> Result<Self, Error>
    where
        Self: Sized,
    {
        match value {
            Value::Datetime(n) => Ok(MyOwnDateTime(n.timestamp_millis())),
            _ => Err(Error::thrown("No good".to_string())),
        }
    }
}

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();

    db.use_ns("main").use_db("main").await.unwrap();

    println!(
        "{:?}",
        db.query("time::now()")
            .await
            .unwrap()
            .take::<Option<MyOwnDateTime>>(0)
    );
}
```

An example of successful and unsuccessful conversions into the user-created `MyOwnDateTime` struct:

```rust
#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();

    db.use_ns("main").use_db("main").await.unwrap();

    println!(
        "{:?}\n",
        db.query("time::now()")
            .await
            .unwrap()
            .take::<Option<MyOwnDateTime>>(0)
    );

    println!(
        "{:?}",
        db.query("CREATE person")
            .await
            .unwrap()
            .take::<Option<MyOwnDateTime>>(0)
    );
}
```

Output:

```text
Ok(Some(MyOwnDateTime(1760330504574)))

Err(InternalError("Couldn't convert Object(Object({\"id\": RecordId(RecordId { table: \"person\", key: String(\"tcblzaktx3ponin9dyci\") })})) to MyOwnDateTime"))
```

https://github.com/orgs/surrealdb/discussions/3389
### Getting the SurrealQL type of a Rust type

`kind_of()` returns a `Kind`, and `to_sql()` on a `Kind` gives the SurrealQL type as a string, ready for a `DEFINE FIELD ... TYPE` clause. An `Option<String>` gives `none | string`, which is the form SurrealQL also stores for `option<string>`.

```rust
use surrealdb::types::{RecordId, SurrealValue, ToSql};

#[derive(SurrealValue)]
struct Person {
    id: RecordId,
    name: String,
    nickname: Option<String>,
    tags: Vec<String>,
}

fn main() {
    println!("{}", Option::<String>::kind_of().to_sql());
    println!("{}", Vec::<String>::kind_of().to_sql());
    println!("{}", RecordId::kind_of().to_sql());
    println!("{}", Person::kind_of().to_sql());
}
```

```text title="Output"
none | string
array<string>
record
{id: record, name: string, nickname: none | string, tags: array<string>}
```
--END--

## Record IDs as strings

A record's `id` is a `RecordId`, made of a `table` and a `key`, so a struct field that holds it has to be a `RecordId` rather than a `String`. Reading it into a `String` field fails with `Failed to deserialize field 'id' ...: Expected string, got record`. There are two ways to work with the ID as a string instead.

The first is to keep a `RecordId` field and convert it where a string is needed. `to_sql()`, from the `ToSql` trait, gives the ID in SurrealQL form, and `key.to_sql()` gives the key alone:

```rust
use surrealdb::types::{RecordId, SurrealValue, ToSql};

#[derive(Debug, SurrealValue)]
struct Person {
    id: RecordId,
    name: String,
}

let person: Option<Person> = db.select(("person", "tobie")).await?;
if let Some(person) = person {
    println!("{}", person.id.to_sql());     // person:tobie
    println!("{}", person.id.key.to_sql()); // tobie
}
```

The second is to have the query return strings, and use a `String` field. `<string> id` casts the whole ID, and `<string> record::id(id)` returns the key alone, which suits an ID that your application generates and shares with other systems:

```rust
#[derive(Debug, SurrealValue)]
struct PersonSummary {
    id: String,
    name: String,
}

let mut response = db
    .query("SELECT <string> record::id(id) AS id, name FROM person")
    .await?;
let people: Vec<PersonSummary> = response.take(0)?;
// [PersonSummary { id: "tobie", name: "Tobie" }]
```

A key that needs escaping keeps its backticks in the full form, as in ``person:`a-b` ``, while `record::id` returns the key as it was written (`a-b`). Use the full form for a value that is later turned back into a record ID, since the key alone does not say which table it belongs to.

`RecordId::parse_simple()` turns the full form back into a `RecordId`. It splits the string at the first colon outside backticks and removes backtick escaping. It returns an error for a string with no colon:

```rust
use surrealdb::types::RecordId;

let id = RecordId::parse_simple("person:tobie")?;
let person: Option<Person> = db.select(id).await?;
```

The key is always parsed as a string. For example, `person:1` becomes a record ID with the string key `'1'`, and that ID does not match a record created with the number key `1`. For a key that is not a string, build the ID with `RecordId::new()`, or pass the whole string to `type::record()` in the query. That function reads the key as SurrealQL, so `type::record('person:1')` returns `person:1` with a number key.

### Numeric record IDs

The same two approaches work for a record whose key is a number, such as `user:45`. A struct field of type `i64` cannot hold the whole ID, and reading one into it fails with `Failed to deserialize field 'id' on type 'User': Expected int, got record`. To get the number into an `i64` field, return the key alone from the query with `record::id`:

```rust
#[derive(Debug, SurrealValue)]
struct User {
    id: i64,
    email: String,
}

let users: Vec<User> = db
    .query("SELECT record::id(id) AS id, email FROM user")
    .await?
    .take(0)?;
// [User { id: 45, email: "ann@example.com" }]
```

To keep a `RecordId` field instead, match on its key, which is a `RecordIdKey`:

```rust
use surrealdb::types::RecordIdKey;

if let RecordIdKey::Number(n) = user.id.key {
    println!("{n}"); // 45
}
```

### Passing a record ID into a query

A string that looks like a record ID is still a string, so it never equals a record ID. Binding `format!("category:{}", key)` and comparing it with `id` returns no records. Bind a `RecordId` instead, or convert the string in the query with [`type::record()`](/docs/reference/query-language/functions/database-functions/type.md#typerecord):

```rust
use surrealdb::types::RecordId;

// Returns nothing: $id is the string 'category:books'
db.query("SELECT * FROM category WHERE id = $id")
    .bind(("id", format!("category:{}", "books")))
    .await?;

// Returns category:books
db.query("SELECT * FROM category WHERE id = $id")
    .bind(("id", RecordId::new("category", "books")))
    .await?;

// Also returns category:books
db.query("SELECT * FROM type::record('category', $key)")
    .bind(("key", "books"))
    .await?;
```

The key must also have the same type as the key of the stored record. A record created with a UUID key, such as `category:u'019d764a-c692-7631-a182-de9d745eb127'`, is not found with the UUID as a string. Cast the key in the query with `type::record('category', <uuid> $key)`, or bind a `RecordId` whose key is a UUID.

### Record IDs in a JSON response

`RecordId` implements serde's `Serialize`, so a struct that holds one can be returned from a web handler, such as an Axum `Json<Person>`. The ID is serialised as an object of its two parts, the `table` and the `key`, and the key carries the name of its type:

```json
{ "id": { "table": "person", "key": { "String": "tobie" } }, "name": "Tobie" }
```

To send the key on its own, as most HTTP APIs do, convert the record into a response type with a `String` field. Matching on `RecordIdKey::String` gives the key as it was written, and `to_sql()` covers keys of other types, such as a number:

```rust
use serde::Serialize;
use surrealdb::types::{RecordId, RecordIdKey, SurrealValue, ToSql};

#[derive(SurrealValue)]
struct Person {
    id: RecordId,
    name: String,
}

#[derive(Serialize)]
struct PersonResponse {
    id: String,
    name: String,
}

impl From<Person> for PersonResponse {
    fn from(person: Person) -> Self {
        let id = match person.id.key {
            RecordIdKey::String(key) => key,
            other => other.to_sql(),
        };
        Self { id, name: person.name }
    }
}
```

```json title="Output"
{ "id": "tobie", "name": "Tobie" }
```

The table name is in `person.id.table`, and `person.id.table.to_sql()` returns it as a string. A route such as `/person/{id}` then needs only the key, and the handler rebuilds the record ID from the table it serves, for example with `db.select(("person", id))`. A key built from a path parameter is always a string, so a table whose keys are numbers needs the number parsed first; see [Numeric record IDs](#numeric-record-ids).

### Fields that link to another record

A field of type `record<category>` holds the ID of a category, not the category itself. The struct field that writes it is therefore a `RecordId`. A struct shaped like the category record is refused, because its value is an object:

```rust
use surrealdb::types::{RecordId, SurrealValue};

#[derive(Debug, SurrealValue)]
struct Product {
    name: String,
    category: RecordId,
}

let product: Option<Product> = db
    .create("product")
    .content(Product {
        name: "Apple".into(),
        category: RecordId::new("category", "fruits"),
    })
    .await?;
```

With a nested `Category` struct in place of the `RecordId`, the same call fails with ``Couldn't coerce value for field `category` ...: Expected `record<category>` but found `{ name: 'Fruits' }` ``.

To read the category's fields along with the product, fetch the link with `.*` in the query, and use a struct that holds the category. `.*` returns the whole linked record, including its `id`, so each struct can keep its own ID as a `RecordId`:

```rust
#[derive(Debug, SurrealValue)]
struct Category {
    id: RecordId,
    name: String,
}

#[derive(Debug, SurrealValue)]
struct ProductWithCategory {
    id: RecordId,
    name: String,
    category: Category,
}

let products: Vec<ProductWithCategory> = db
    .query("SELECT id, name, category.* AS category FROM product")
    .await?
    .take(0)?;
// [ProductWithCategory { id: product:apple, name: "Apple", category: Category { id: category:fruits, name: "Fruits" } }]
```

## Fields the database sets

A field that the database fills in, such as one with a `DEFAULT` or one that is `READONLY`, can share a struct with the fields your code sets. Make it an `Option`. `SurrealValue` sends `None` as `NONE`, which is what `DEFAULT` replaces, so the same struct can create the record and read it back:

```rust
// DEFINE FIELD order ON item TYPE int DEFAULT 7 READONLY;
#[derive(Debug, SurrealValue)]
struct Item {
    id: Option<RecordId>,
    name: String,
    order: Option<i64>,
}

let created: Option<Item> = db
    .create(("item", "one"))
    .content(Item { id: None, name: "a".into(), order: None })
    .await?;
// Some(Item { id: Some(item:one), name: "a", order: Some(7) })
```

Sending `None` for a `READONLY` field in a later `.content()` update keeps the stored value. Sending a different value fails with ``Found changed value for field `order` ... but field is readonly``.

## Datetimes with chrono

`chrono::DateTime<Utc>` implements `SurrealValue`, and converts to and from a SurrealQL `datetime`. A field of that type in a struct that derives `SurrealValue` is stored as a `datetime`, so it can be compared with other datetimes in a query. Add `chrono` as a dependency of your project to use the type.

```rust
use chrono::{DateTime, TimeZone, Utc};
use surrealdb::engine::any::connect;
use surrealdb::opt::auth::Root;
use surrealdb::types::{RecordId, SurrealValue};

#[derive(Debug, SurrealValue)]
struct Event {
    name: String,
    at: DateTime<Utc>,
}

#[derive(Debug, SurrealValue)]
struct EventRecord {
    id: RecordId,
    name: String,
    at: DateTime<Utc>,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("ws://localhost:8000").await?;

    db.signin(Root {
        username: "root".to_string(),
        password: "secret".to_string(),
    })
    .await?;

    db.use_ns("ns").use_db("db").await?;

    let _: Option<EventRecord> = db
        .create("event")
        .content(Event {
            name: "Launch".to_string(),
            at: Utc.with_ymd_and_hms(2025, 10, 18, 4, 24, 17).unwrap(),
        })
        .await?;

    // The field is a datetime, so it compares with a bound DateTime<Utc>
    // or with a d'' literal
    let recent: Vec<EventRecord> = db
        .query("SELECT * FROM event WHERE at > $since")
        .bind(("since", Utc.with_ymd_and_hms(2025, 10, 15, 0, 0, 0).unwrap()))
        .await?
        .take(0)?;
    dbg!(recent);
    Ok(())
}
```

A datetime that reaches SurrealDB as a string is stored as a string. This happens with a field of type `String` holding a formatted date, such as the output of `to_rfc3339()`, and with records written by the 2.x SDK from a struct that derived `serde::Serialize`. A string is never greater than a datetime, so a query such as `WHERE at > d'2025-10-15'` does not find it. To keep a string field while querying it as a datetime, cast it in the query with `<datetime> at`, or define the field with `TYPE datetime` so that SurrealDB refuses a string for it when it is written, which shows the problem immediately.

## Convenience methods for the `Value` type

Importing the `SurrealValue` trait gives access to a lot of convenience methods.

One example is the `.into_value()` method which converts a large number of Rust standard library types into a SurrealQL `Value`.

```rust
use surrealdb_types::{SurrealValue, Value};

fn main() {
    let string_val = "string".into_value();
    assert!(string_val.is_string());
    assert_eq!(string_val, Value::String("string".into()));
}
```

One more example of `.into_value()` to convert a `HashMap<String, &'str>` into a `Value`:

```rust
use std::collections::HashMap;

use surrealdb::engine::any::connect;
use surrealdb_types::{SurrealValue, Value};

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("db").use_db("db").await.unwrap();

    let mut map = HashMap::new();
    map.insert("name".to_string(), "Billy");
    map.insert("id".to_string(), "person:one");

    // Turn HashMap into SurrealDB Value
    let as_person = map.into_value();

    // Object(Object({"id": String("person:one"), "name": String("Billy")}))
    println!("{as_person:?}");

    // Insert it into a query to create a record
    let res = db
        .query("CREATE ONLY person CONTENT $person")
        .bind(("person", as_person))
        .await
        .unwrap()
        .take::<Value>(0)
        .unwrap();

    // Object(Object({"id": RecordId(RecordId { table: "person", key: String("person:one") }), "name": String("Billy")}))
    println!("{res:?}");
}
```

A `Value` can be manually constructed using any of the various structs and enums contained within it. This is particularly useful when constructing a complex ID made up of a table name and an array for the key.

```rust
use std::str::FromStr;

use surrealdb::engine::any::connect;
use surrealdb_types::{Array, Datetime, RecordId, RecordIdKey, Value};

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("db").use_db("db").await.unwrap();

    let date = "2025-10-13T05:16:11.343Z";

    let complex_id = RecordId {
        table: "weather".into(),
        key: RecordIdKey::Array(Array::from(vec![
            Value::String("London".to_string()),
            Value::Datetime(Datetime::from_str(date).unwrap()),
        ])),
    };

    let mut res = db
        .query("CREATE ONLY weather SET id = $id")
        .bind(("id", complex_id))
        .await
        .unwrap();

    // Object(Object({"id": RecordId(RecordId { table: "weather", key: Array(Array([String("London"), Datetime(Datetime(2025-10-13T05:16:11.343Z))])) })}))
    println!("{:?}", res.take::<Value>(0).unwrap());
}
```

The `.is()` method for a `Value` returns `true` if the type(s) in question can be converted to the type indicated when the method is called.

```rust
use std::collections::HashMap;
use surrealdb_types::SurrealValue;

fn main() {
    // true
    println!("{}", "string".into_value().is::<String>());

    let mut map = HashMap::new();
    map.insert("name".to_string(), "Billy");
    map.insert("id".to_string(), "person:one");

    // true
    println!("{}", map.clone().into_value().is::<HashMap<String, &str>>());
    // Also true
    println!("{}", map.into_value().is::<HashMap<String, String>>());
}
```

A `Value` can be converted into a `serde_json::Value` using the `.into_json_value()` method, and vice versa using `.into_value()`.

```rust
use surrealdb::engine::any::connect;
use surrealdb_types::{SurrealValue, Value};

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("db").use_db("db").await.unwrap();

    let value = db
        .query("CREATE ONLY person:one SET age = 21")
        .await
        .unwrap()
        .take::<Value>(0)
        .unwrap();

    // Object(Object({"age": Number(Int(21)), "id": RecordId(RecordId { table: "person", key: String("one") })}))
    println!("{value:?}");
    // Object {"age": Number(21), "id": String("person:one")}
    println!("{:?}", value.clone().into_json_value());

    // Round trip
    value.into_json_value().into_value();
}
```

## Value construction macros

Alongside `kind!`, the crate exports four macros for building values by hand: `object!`, `array!`, `set!` and `vars!`. Each is available from `surrealdb::types` as well as from `surrealdb_types` directly.

| Macro | Builds | Notes |
| ----- | ------ | ----- |
| `object!` | `Object` | Keys can be bare identifiers or quoted string literals |
| `array!` | `Array` | Uses square brackets, like `vec!` |
| `set!` | `Set` | Deduplicates its items; takes `Value`s, not raw Rust values |
| `vars!` | `Variables` | Same syntax as `object!`, used for `.bind()` after `.query()` |

Values passed to `object!`, `array!` and `vars!` only need to implement `SurrealValue`, so ordinary Rust types can be used directly.

```rust
use surrealdb::types::{Value, array, object, set};

fn main() {
    let obj = object! {
        name: "Aeon",
        age: 30,
        "home-town": "Bregna",
    };

    let arr = array![1, "two", true];

    let tags = set! {
        Value::from_t("rust"),
        Value::from_t("surrealdb"),
        Value::from_t("rust"),
    };

    println!("{obj:?}");
    println!("{arr:?}");
    println!("{tags:?}");
}
```

Output:

```text
Object({"age": Number(Int(30)), "home-town": String("Bregna"), "name": String("Aeon")})
Array([Number(Int(1)), String("two"), Bool(true)])
Set({String("rust"), String("surrealdb")})
```

Note the two differences between `array!` and `set!`. `set!` drops the duplicate `"rust"`, and it does not convert its items for you: each one must already be a `Value`. Passing `set! { 1, 2, 3 }` will not compile, while `array![1, 2, 3]` will.

### The `vars!` macro

`vars!` builds a `Variables` map, which is what the [`.bind()`](/docs/reference/rust/methods/query.md#binding-parameters) method on a query takes. It is the most direct way to pass more than one parameter into a query.

```rust
use surrealdb::engine::any::connect;
use surrealdb::types::{RecordId, SurrealValue, vars};

#[derive(Debug, SurrealValue)]
struct Person {
    id: RecordId,
    name: String,
    age: i64,
}

#[tokio::main]
async fn main() -> surrealdb::Result<()> {
    let db = connect("mem://").await?;
    db.use_ns("main").use_db("main").await?;

    let sql = "
        CREATE type::table($table) SET name = $name, age = $age;
        SELECT * FROM type::table($table) WHERE age >= $min_age;
    ";

    let mut result = db
        .query(sql)
        .bind(vars! {
            table: "person",
            name: "Aeon",
            age: 30,
            min_age: 18,
        })
        .await?;

    let created: Option<Person> = result.take(0)?;
    dbg!(created);
    let adults: Vec<Person> = result.take(1)?;
    dbg!(adults);
    Ok(())
}
```

`Variables` is an ordinary struct as well as a macro target. `Variables::new()` followed by `.insert()` builds the same value at runtime, which is the better choice when the set of parameters is not known when the code is written.

## The `kind!` macro

The crate includes a `kind!` macro which allows a SurrealQL type to be used directly instead of its Rust equivalent.

This macro is especially useful when working with types like [literals](/docs/reference/query-language/language-primitives/data-types/literals.md) which are similar to enums but can specify exact possible values in a way that Rust would require deriving `TryFrom` to work. In this case, the `SurrealValue` trait can be implemented manually and the `kind!` macro used for its `kind_of()` method.

```rust
fn kind_of() -> surrealdb_types::Kind {
    kind!({ status: "good" } | { status: "goodwithnotification", notification: string} | { status: "error", at: datetime, reason: string })
}
```

This is technically possible without the macro, but requires a lot more boilerplate. Here is the output when using `cargo expand` to show the generated code for the example above.

```rust
fn kind_of() -> surrealdb_types::Kind {
    surrealdb_types::Kind::Either(
        vec!([
            surrealdb_types::Kind::Literal(
                surrealdb_types::KindLiteral::Object(
                    std::collections::BTreeMap::from([
                        (
                            "status".to_string(),
                            surrealdb_types::Kind::Literal(
                                surrealdb_types::KindLiteral::String("good".to_string()),
                            ),
                        ),
                    ]),
                ),
            ),
            surrealdb_types::Kind::Literal(
                surrealdb_types::KindLiteral::Object(
                    std::collections::BTreeMap::from([
                        (
                            "status".to_string(),
                            surrealdb_types::Kind::Literal(
                                surrealdb_types::KindLiteral::String(
                                    "goodwithnotification".to_string(),
                                ),
                            ),
                        ),
                        ("notification".to_string(), surrealdb_types::Kind::String),
                    ]),
                ),
            ),
            surrealdb_types::Kind::Literal(
                surrealdb_types::KindLiteral::Object(
                    std::collections::BTreeMap::from([
                        (
                            "status".to_string(),
                            surrealdb_types::Kind::Literal(
                                surrealdb_types::KindLiteral::String("error".to_string()),
                            ),
                        ),
                        ("at".to_string(), surrealdb_types::Kind::Datetime),
                        ("reason".to_string(), surrealdb_types::Kind::String),
                    ]),
                ),
            ),
            ]),
        ),
}
```

The following example shows the `kind!` macro used for a Rust enum that manually implements `SurrealValue`, along with examples of its use from the Rust side to the SurrealDB side, and vice versa.

```rust
use surrealdb::engine::any::connect;
use surrealdb_types::{Datetime, Error, Object, SurrealValue, ToSql, Value, kind};

#[derive(SurrealValue)]
struct MyError {
    at: Datetime,
    reason: String,
}

enum Response {
    Good,
    GoodWithNotification(String),
    Error(MyError),
}

impl SurrealValue for Response {
    fn kind_of() -> surrealdb_types::Kind {
        kind!({ status: "good" } | { status: "goodwithnotification", notification: string} | { status: "error", at: datetime, reason: string })
    }

    fn into_value(self) -> Value {
        let mut obj = Object::new();
        match self {
            Response::Good => {
                obj.insert("status", "good");
            }
            Response::GoodWithNotification(n) => {
                obj.insert("status", "goodwithnotification");
                obj.insert("notification", n);
            }
            Response::Error(e) => {
                obj.insert("status", "error");
                obj.insert("at", e.at);
                obj.insert("reason", e.reason);
            }
        }
        Value::Object(obj)
    }

    fn from_value(value: Value) -> Result<Self, Error>
    where
        Self: Sized,
    {
        let Value::Object(o) = value else {
            return Err(Error::thrown("Should have been an object".to_string()));
        };
        let Some(Value::String(status)) = o.get("status") else {
            return Err(Error::thrown(
                "Error trying to get 'status' field".to_string(),
            ));
        };
        match status.as_str() {
            "Good" => Ok(Response::Good),
            status @ "GoodWithNotification" => {
                Ok(Response::GoodWithNotification(status.to_string()))
            }
            "Error" => {
                let Some(Value::Datetime(at)) = o.get("at") else {
                    return Err(Error::thrown("Error trying to get 'at' field".to_string()));
                };
                let Some(Value::String(reason)) = o.get("reason") else {
                    return Err(Error::thrown(
                        "Error trying to get 'reason' field".to_string(),
                    ));
                };
                Ok(Response::Error(MyError {
                    at: at.clone(),
                    reason: reason.clone(),
                }))
            }
            _ => Err(Error::thrown("No status field for some reason".to_string())),
        }
    }

    fn is_value(value: &Value) -> bool {
        value.is_kind(&Self::kind_of())
    }
}

#[tokio::main]
async fn main() {
    let db = connect("memory").await.unwrap();
    db.use_ns("main").use_db("main").await.unwrap();

    // Turning DB results into Rust enum
    let mut statuses = db.query("
        { status: 'Good' };
        { status: 'GoodWithNotification', notification: 'We need things to make us go. We need help.' };
        { status: 'Error', at: d'1914-07-28', reason: 'General conflagration'};
    ").await.unwrap();

    println!(
        "Good: {}",
        statuses
            .take::<Option<Response>>(0)
            .unwrap()
            .unwrap()
            .into_value()
            .to_sql_pretty()
    );
    println!(
        "Good with notification: {}",
        statuses
            .take::<Option<Response>>(1)
            .unwrap()
            .unwrap()
            .into_value()
            .to_sql_pretty()
    );
    println!(
        "Error: {}",
        statuses
            .take::<Option<Response>>(2)
            .unwrap()
            .unwrap()
            .into_value()
            .to_sql_pretty()
    );

    // Turn Rust enum into Values,
    // use them in the CONTENT clause
    // and then print the result
    let good = Response::Good;
    let good_but = Response::GoodWithNotification("Keep it up!".into());
    let error = Response::Error(MyError {
        at: Datetime::now(),
        reason: "Error: can't think of interesting error message".into(),
    });

    println!(
        "Good: {:?}",
        db.query("CREATE result CONTENT $content")
            .bind(("content", good))
            .await
            .unwrap()
            .take::<Option<Value>>(0)
            .unwrap()
            .unwrap()
            .to_sql()
    );
    println!(
        "Good but: {:?}",
        db.query("CREATE result CONTENT $content")
            .bind(("content", good_but))
            .await
            .unwrap()
            .take::<Option<Value>>(0)
            .unwrap()
            .unwrap()
            .to_sql()
    );
    println!(
        "Error: {:?}",
        db.query("CREATE result CONTENT $content")
            .bind(("content", error))
            .await
            .unwrap()
            .take::<Option<Value>>(0)
            .unwrap()
            .unwrap()
            .to_sql()
    );
}
```
