#[derive(Embed)]
{
// Attributes available to this derive:
#[column]
#[document]
#[index]
#[unique]
#[shared]
}
Expand description
Derive macro that turns a struct or enum into an embedded type stored inline in a parent model’s table.
Embedded types do not have their own tables or primary keys. Their
fields are flattened into the parent model’s columns. Use Embed for
value objects (addresses, coordinates, metadata) and enums
(status codes, contact info variants).
§Structs
An embedded struct’s fields become columns in the parent table, prefixed
with the field name. For example, an address: Address field with
street and city produces columns address_street and
address_city.
#[derive(toasty::Embed)]
struct Address {
street: String,
city: String,
}
#[derive(toasty::Model)]
struct User {
#[key]
#[auto]
id: i64,
name: String,
address: Address,
}Applying #[derive(Embed)] to a struct generates:
- An
Embedtrait implementation (idandschemamethods). - A
Fieldsstruct returned by<Type>::fields()for building filter expressions on individual fields. - An
Updatestruct used by the parent model’s update builder for partial field updates.
§Nesting
Embedded structs can contain other embedded types. Columns are flattened with chained prefixes:
#[derive(toasty::Embed)]
struct Location {
lat: i64,
lon: i64,
}
#[derive(toasty::Embed)]
struct Address {
street: String,
city: Location,
}When Address is embedded as address in a parent model, this
produces columns address_street, address_city_lat, and
address_city_lon.
§Enums
An embedded enum stores a discriminant value identifying the active
variant. By default, Toasty derives a string label for each variant by
converting its Rust name to snake_case. Use
#[column(rename_all = "...")] on the enum to select another naming
convention, or #[column(variant = "...")] on a variant to set one label.
Unit-only enum:
#[derive(toasty::Embed)]
enum Status {
Pending,
InProgress,
Archived,
}A unit-only enum occupies a single column in the parent table. The
example stores the labels pending, in_progress, and archived.
Data-carrying enum:
#[derive(toasty::Embed)]
enum ContactInfo {
Email { address: String },
Phone { number: String },
}A data-carrying enum stores the discriminant column plus one nullable
column per variant field. For example, a contact: ContactInfo field
produces columns contact (discriminant), contact_address, and
contact_number. Only the columns belonging to the active variant
contain values; the rest are NULL.
Mixed enum (unit and data variants together):
#[derive(toasty::Embed)]
enum Status {
Pending,
Failed { reason: String },
Done,
}Applying #[derive(Embed)] to an enum generates:
- An
Embedtrait implementation (idandschemamethods). - A
Fieldsstruct withis_<variant>()methods and comparison methods (eq,ne,in_list). - For data-carrying variants, per-variant handle types with a
matches(closure)method for pattern matching and field access.
§Newtype Auto proxying
A tuple-newtype embedded struct (one unnamed field) automatically
implements Auto whenever its inner type does — no annotation
required. Toasty emits a NewtypeOf marker carrying the inner type
and a blanket Auto impl resolves through it:
#[derive(toasty::Embed)]
struct UserId(uuid::Uuid);
#[derive(toasty::Model)]
struct User {
#[key]
#[auto]
id: UserId,
name: String,
}Newtypes wrapping non-Auto types stay non-Auto; nesting works
transparently (Outer(Inner(u64)) proxies through both layers).
§Attributes
§#[column(...)] — customize the database column
On struct fields, overrides the column name and/or type:
#[derive(toasty::Embed)]
struct Address {
#[column("addr_street")]
street: String,
#[column(type = varchar(255))]
city: String,
}See Model for the full list of supported column
types.
Changing stored enum discriminants. On an enum,
#[column(rename_all = "...")] changes how Toasty derives string labels
for variants without an explicit label:
#[derive(toasty::Embed)]
#[column(rename_all = "SCREAMING_SNAKE_CASE")]
enum PartyKind {
Customer,
PreferredSupplier,
}This example uses the labels CUSTOMER and PREFERRED_SUPPLIER. Without
rename_all, Toasty uses snake_case.
The supported rules and their result for PreferredSupplier are:
| Rule | Label |
|---|---|
lowercase | preferredsupplier |
UPPERCASE | PREFERREDSUPPLIER |
PascalCase | PreferredSupplier |
camelCase | preferredSupplier |
snake_case | preferred_supplier |
SCREAMING_SNAKE_CASE | PREFERRED_SUPPLIER |
kebab-case | preferred-supplier |
SCREAMING-KEBAB-CASE | PREFERRED-SUPPLIER |
Use #[column(variant = "...")] to set individual labels:
#[derive(toasty::Embed)]
enum PartyKind {
#[column(variant = "customer")]
Customer,
#[column(variant = "preferred-supplier")]
PreferredSupplier,
}An explicit variant label takes precedence over rename_all when an enum
uses both attributes.
String-label enums use Toasty’s enum storage by default. Use
#[column(type = enum("type_name"))] to set the database enum type name,
or #[column(type = text)] or #[column(type = varchar(N))] to use a
plain string column. rename_all changes variant labels only; it does not
change the enum type name.
To store integers instead, assign an integer to every variant:
#[derive(toasty::Embed)]
enum Priority {
#[column(variant = 10)]
Low,
#[column(variant = 20)]
High,
}An enum cannot mix string and integer discriminants. Integer discriminants
are stored as i64 and do not support rename_all. All discriminant values
must be unique. String labels may contain at most 63 bytes.
§#[index] — add a database index
Creates a non-unique index on the field’s flattened column.
#[derive(toasty::Embed)]
struct Contact {
#[index]
country: String,
}§#[unique] — add a unique constraint
Creates a unique index on the field’s flattened column. The database enforces uniqueness.
#[derive(toasty::Embed)]
struct Contact {
#[unique]
email: String,
}§#[shared(ident)] — share a column across enum variants
Declares a shared logical field on the enum. Variant fields declaring
the same identifier are backed by a single nullable column instead of
one column per variant. The identifier — not the Rust field names,
which may differ per variant — names the field: the column name derives
from it ({enum_field}_{ident}), and enum-level #[index] /
#[unique] attributes reference it.
#[derive(toasty::Embed)]
enum Creature {
#[column(variant = 1)]
Human {
#[shared(name)]
full_name: String,
profession: String,
},
#[column(variant = 2)]
Animal {
#[shared(name)]
nickname: String,
species: String,
},
}
// Columns: creature, creature_name (shared), creature_profession,
// creature_speciesFields sharing an identifier must have the same type. To rename the
shared column, add #[column("...")] to any one member of the group
(if several declare it, they must agree):
#[shared(name)]
#[column("legacy_name")]
name: String,§Enum-level #[index(...)] / #[unique(...)]
On the enum itself, #[index(...)] and #[unique(...)] create an
index over variant-field columns. Each reference is a shared field
identifier or a variant::field path naming a variant field that owns
its column; the two forms compose into composite indices.
#[derive(toasty::Embed)]
#[unique(name)]
#[index(name, human::profession)]
enum Creature {
#[column(variant = 1)]
Human {
#[shared(name)]
name: String,
profession: String,
},
#[column(variant = 2)]
Animal {
#[shared(name)]
name: String,
},
}An index on a shared column covers rows of every variant: with
#[unique(name)] above, a Human named “Bob” and an Animal named
“Bob” conflict. Rows of variants that do not declare the shared field
store NULL and never conflict. For this reason, field-level
#[index] / #[unique] on a #[shared] field is a compile error
pointing at the enum-level form.
§Using embedded types in a model
Reference an embedded type as a field on a Model
struct. The parent model’s create and update builders gain a setter for
the embedded field. Partial updates of individual sub-fields use
stmt::patch:
use toasty::stmt;
// Full replacement
user.update()
.address(Address { street: "456 Oak Ave".into(), city: "Seattle".into() })
.exec(&mut db).await?;
// Partial update — updates city, leaves street unchanged
user.update()
.address(stmt::patch(Address::fields().city(), "Portland"))
.exec(&mut db).await?;Embedded struct fields are queryable through the parent model’s
fields() accessor:
let users = User::filter(User::fields().address().city().eq("Seattle"))
.exec(&mut db).await?;§Constraints
- Embedded structs must have named fields (tuple structs are not supported).
- Generic parameters are not supported.
- Enum discriminants must all be strings or all be integers. Integer discriminants must be specified on every variant.
#[column(rename_all = "...")]applies only to string labels.- Enum variants may be unit variants or have named fields. Tuple variants are not supported.
- Embedded types cannot have primary keys, relations,
#[auto],#[default], or#[update]attributes.
§Full example
#[derive(Debug, PartialEq, toasty::Embed)]
#[column(rename_all = "SCREAMING_SNAKE_CASE")]
enum Priority {
Low,
Normal,
High,
}
#[derive(Debug, toasty::Embed)]
struct Metadata {
version: i64,
status: String,
priority: Priority,
}
#[derive(Debug, toasty::Model)]
struct Document {
#[key]
#[auto]
id: i64,
title: String,
#[unique]
slug: String,
meta: Metadata,
}
// Create
let mut doc = Document::create()
.title("Design doc")
.slug("design-doc")
.meta(Metadata {
version: 1,
status: "draft".to_string(),
priority: Priority::Normal,
})
.exec(&mut db).await?;
// Query by embedded field
let drafts = Document::filter(
Document::fields().meta().status().eq("draft")
).exec(&mut db).await?;
// Partial update
use toasty::stmt;
doc.update()
.meta(stmt::apply([
stmt::patch(Metadata::fields().version(), 2),
stmt::patch(Metadata::fields().status(), "published"),
]))
.exec(&mut db).await?;