Skip to content

Navigation Menu

Sign in
Sign up

Атрибуты

github-actions[bot] edited this page Jul 27, 2026 · 20 revisions

Полное руководство по всем атрибутам, поддерживаемым entity-derive.

Атрибуты уровня сущности

Применяются к структуре с помощью #[entity(...)]:

#[derive(Entity)]
#[entity(
 table = "users",
 schema = "core",
 sql = "full",
 dialect = "postgres",
 uuid = "v7",
 soft_delete,
 returning = "full",
 error = "AppError",
 events,
 hooks,
 commands,
 transactions
)]
pub struct User { /* ... */ }

Краткий справочник

Атрибут Обязательный По умолчанию Описание
table Да Имя таблицы БД
schema Нет "public" Схема БД
sql Нет "full" Уровень генерации SQL
dialect Нет "postgres" Диалект БД
uuid Нет "v7" Версия UUID для генерации ID
soft_delete Нет false Включить мягкое удаление
returning Нет "full" Режим RETURNING
upsert(...) Нет Генерация upsert-метода через INSERT ... ON CONFLICT
api(guard = "...") Нет Принудительный FromRequestParts-гвард в генерируемых хендлерах
error Нет sqlx::Error Пользовательский тип ошибки
events Нет false Генерировать события жизненного цикла
hooks Нет false Генерировать трейт хуков
commands Нет false Включить паттерн CQRS-команд
transactions Нет false Генерировать адаптер для транзакций

table (обязательный)

Имя таблицы базы данных.

#[entity(table = "users")] // → FROM users
#[entity(table = "user_profiles")] // → FROM user_profiles

schema (опциональный)

Схема базы данных. По умолчанию: "public".

#[entity(table = "users")] // → FROM public.users
#[entity(table = "users", schema = "core")] // → FROM core.users
#[entity(table = "users", schema = "auth")] // → FROM auth.users

sql (опциональный)

Уровень генерации SQL. По умолчанию: "full".

Значение Repository Trait Реализация PgPool Применение
"full" Да Да Стандартные CRUD-сущности
"trait" Да Нет Пользовательские запросы (joins, CTE)
"none" Нет Нет Только DTO, без БД
#[entity(table = "users", sql = "full")] // Полная автоматизация (по умолчанию)
#[entity(table = "users", sql = "trait")] // Только трейт, SQL реализуете сами
#[entity(table = "users", sql = "none")] // Нет слоя БД вообще

dialect (опциональный)

Диалект БД для генерации SQL. По умолчанию: "postgres".

Диалект Алиасы Тип клиента Статус
"postgres" "pg", "postgresql" sqlx::PgPool Стабильно
"clickhouse" "ch" clickhouse::Client Не реализовано — ошибка компиляции
"mongodb" "mongo" mongodb::Client Не реализовано — ошибка компиляции

uuid (опциональный)

Версия UUID для автоматически генерируемых первичных ключей. По умолчанию: "v7".

Версия Метод Свойства
"v7" Uuid::now_v7() Упорядочен по времени, сортируемый (рекомендуется)
"v4" Uuid::new_v4() Случайный, широко совместимый
#[entity(table = "users", uuid = "v7")] // Упорядоченный по времени (по умолчанию)
#[entity(table = "sessions", uuid = "v4")] // Случайный UUID

Почему UUID v7?

  • Упорядочен по времени: естественная сортировка по времени создания
  • Лучшая производительность индексов БД
  • Не требует координации (в отличие от sequences)
  • Глобально уникален в распределённых системах

soft_delete (опциональный)

Включает мягкое удаление для пометки записей как удалённых вместо их удаления.

#[derive(Entity)]
#[entity(table = "documents", soft_delete)]
pub struct Document {
 #[id]
 pub id: Uuid,
 #[field(create, response)]
 pub title: String,
 #[field(skip)]
 pub deleted_at: Option<DateTime<Utc>>, // Обязательное поле
}

Генерируемые методы:

  • delete() — Устанавливает deleted_at = NOW() вместо DELETE
  • hard_delete() — Окончательно удаляет запись
  • restore() — Устанавливает deleted_at = NULL
  • find_by_id() / list() — Автоматически фильтрует удалённые записи
  • find_by_id_with_deleted() / list_with_deleted() — Включает удалённые записи

returning (опциональный)

Управляет тем, какие данные возвращаются после INSERT/UPDATE. По умолчанию: "full".

Режим SQL-клауза Применение
"full" RETURNING * Получить все поля включая сгенерированные БД (по умолчанию)
"id" RETURNING id Подтвердить вставку, вернуть готовую сущность
"none" (без RETURNING) Fire-and-forget, самый быстрый вариант
"col1, col2" RETURNING col1, col2 Вернуть определённые колонки
#[entity(table = "logs", returning = "none")] // Самый быстрый
#[entity(table = "users", returning = "full")] // Получить сгенерированные БД значения
#[entity(table = "events", returning = "id, created_at")] // Пользовательские колонки

events(outbox) (опциональный)

Надёжная доставка событий через транзакционный outbox. Просто events лишь генерирует enum; со streams NOTIFY — fire-and-forget. events(outbox) заставляет каждую генерируемую запись вставлять сериализованное событие в таблицу entity_outbox в той же транзакции, а рантайм OutboxDrainer (entity-core, фича outbox) доставляет строки через FOR UPDATE SKIP LOCKED с экспоненциальным backoff и парковкой после max_attempts. At-least-once — обработчики должны быть идемпотентными. Совместимо со streams.

#[derive(Entity, Serialize, Deserialize)]
#[entity(table = "orders", events(outbox), migrations)]
pub struct Order { /* ... */ }
sqlx::query(Order::MIGRATION_OUTBOX).execute(&pool).await?;
struct Notifier;
#[async_trait::async_trait]
impl entity_core::outbox::OutboxHandler for Notifier {
 type Error = anyhow::Error;
 async fn handle(&self, row: &OutboxRow) -> Result<(), Self::Error> {
 deliver(&row.entity, &row.payload).await
 }
}
entity_core::outbox::OutboxDrainer::new(pool, Notifier).run().await;

upsert(...) (опциональный)

Генерирует метод репозитория upsert на основе INSERT ... ON CONFLICT.

#[derive(Entity)]
#[entity(table = "users", upsert(conflict = "email"))]
pub struct User {
 #[id]
 pub id: Uuid,
 #[field(create, response)]
 #[column(unique)]
 pub email: String,
 #[field(create, update, response)]
 pub name: String,
}
Опция Обязательна По умолчанию Описание
conflict Да Колонки конфликта через запятую
action Нет "update" "update" (DO UPDATE) или "nothing" (DO NOTHING)

Генерируется:

  • action = "update"async fn upsert(&self, dto: CreateUserRequest) -> Result<User, Error> — перезаписывает все неконфликтные колонки (DO UPDATE SET col = EXCLUDED.col) и возвращает сохранённую строку
  • action = "nothing"async fn upsert(&self, dto: CreateUserRequest) -> Result<Option<User>, Error> — существующая строка не изменяется; None означает, что конфликтующая строка уже существовала

Проверки на этапе компиляции:

  • колонки конфликта должны существовать и иметь гарантию уникальности (#[id], #[column(unique)] или подходящий unique_index(...))
  • требуется returning = "full" (значение по умолчанию)
  • для action = "update" нужна хотя бы одна неконфликтная обновляемая колонка

При включённых streams upsert публикует уведомление Created для каждой возвращённой строки.

api(guard = "...") (опциональный)

Принудительная аутентификация в генерируемых хендлерах. security = "..." лишь документирует auth в OpenAPI; guard внедряет axum-extractor (FromRequestParts) первым аргументом каждого генерируемого CRUD- и командного хендлера — неудачная экстракция отклоняет запрос до выполнения тела хендлера.

pub struct RequireAuth;
impl<S: Send + Sync> FromRequestParts<S> for RequireAuth {
 type Rejection = StatusCode;
 async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
 parts.headers.contains_key("authorization")
 .then_some(Self)
 .ok_or(StatusCode::UNAUTHORIZED)
 }
}
#[derive(Entity)]
#[entity(table = "users", api(tag = "Users", handlers, guard = "RequireAuth", guard(list = "none")))]
pub struct User { /* ... */ }

Пооперационные оверрайды: guard(create = "Admin", list = "none", ...) с операциями create, get, update, delete, list, commands; литерал "none" отключает гвард. Команды из public = [...] гвард не получают.

error (опциональный)

Пользовательский тип ошибки для репозитория. По умолчанию: sqlx::Error.

#[derive(Debug)]
pub enum AppError {
 Database(sqlx::Error),
 NotFound,
 Validation(String),
}
impl std::error::Error for AppError {}
impl std::fmt::Display for AppError { /* ... */ }
// Обязательно: преобразование из sqlx::Error
impl From<sqlx::Error> for AppError {
 fn from(err: sqlx::Error) -> Self {
 AppError::Database(err)
 }
}
#[derive(Entity)]
#[entity(table = "users", error = "AppError")]
pub struct User { /* ... */ }
// Сгенерированный репозиторий использует AppError:
// impl UserRepository for PgPool {
// type Error = AppError;
// ...
// }

events (опциональный)

Генерирует enum событий жизненного цикла. Подробнее см. События.

#[entity(table = "orders", events)]

Генерируется:

pub enum OrderEvent {
 Created(Order),
 Updated { id: Uuid, changes: UpdateOrderRequest },
 Deleted(Uuid),
}

hooks (опциональный)

Генерирует трейт хуков жизненного цикла. Подробнее см. Хуки.

#[entity(table = "users", hooks)]

Генерируется:

#[async_trait]
pub trait UserHooks: Send + Sync {
 type Error: std::error::Error + Send + Sync;
 async fn before_create(&self, dto: &mut CreateUserRequest) -> Result<(), Self::Error>;
 async fn after_create(&self, entity: &User) -> Result<(), Self::Error>;
 async fn before_update(&self, id: &Uuid, dto: &mut UpdateUserRequest) -> Result<(), Self::Error>;
 async fn after_update(&self, entity: &User) -> Result<(), Self::Error>;
 async fn before_delete(&self, id: &Uuid) -> Result<(), Self::Error>;
 async fn after_delete(&self, id: &Uuid) -> Result<(), Self::Error>;
}

commands (опциональный)

Включает паттерн CQRS-команд. Подробнее см. Команды.

#[entity(table = "users", commands)]
#[command(Register)]
#[command(Deactivate, requires_id)]

transactions (опциональный)

Генерирует адаптер репозитория для типобезопасных транзакций с несколькими сущностями.

#[derive(Entity)]
#[entity(table = "accounts", transactions)]
pub struct Account {
 #[id]
 pub id: Uuid,
 #[field(create, update, response)]
 pub balance: i64,
}

Генерируется:

  • AccountTransactionRepo<'t> — Адаптер репозитория для контекста транзакции
  • Трейт TransactionWithAccount с методом with_accounts()

Использование:

use entity_core::prelude::*;
async fn transfer(pool: &PgPool, from: Uuid, to: Uuid, amount: i64) -> Result<(), AppError> {
 Transaction::new(pool)
 .with_accounts()
 .run(|mut ctx| async move {
 let from_acc = ctx.accounts().find_by_id(from).await?
 .ok_or(AppError::NotFound)?;
 ctx.accounts().update(from, UpdateAccountRequest {
 balance: Some(from_acc.balance - amount),
 }).await?;
 ctx.accounts().update(to, UpdateAccountRequest {
 balance: Some(to_acc.balance + amount),
 }).await?;
 Ok(())
 })
 .await
}

Методы транзакции:

  • create(dto) — Создать сущность в транзакции
  • find_by_id(id) — Найти сущность по ID
  • update(id, dto) — Обновить сущность
  • delete(id) — Удалить сущность (учитывает soft_delete)
  • list(limit, offset) — Список сущностей

Особенности:

  • Автоматический откат при ошибке или панике
  • Типобезопасный паттерн builder
  • Полная поддержка CRUD в транзакции

Атрибуты уровня поля

Применяются к отдельным полям.

#[id]

Отмечает поле первичного ключа.

Поведение:

  • Автоматически генерирует UUID (v7 по умолчанию, настраивается атрибутом uuid)
  • Всегда включается в Response DTO
  • Исключается из CreateRequest и UpdateRequest
#[id]
pub id: Uuid,

#[auto]

Отмечает автоматически генерируемые поля (timestamps, sequences).

Поведение:

  • Получает Default::default() в From<CreateRequest>
  • Исключается из CreateRequest и UpdateRequest
  • Может включаться в Response с #[field(response)]
  • Исключается из генерируемого INSERT, поэтому с migrations non-nullable колонка с временным типом получает соответствующий default в БД: NOW() для TIMESTAMPTZ и TIMESTAMP, CURRENT_DATE для DATE, CURRENT_TIME для TIME. Явный #[column(default = "...")] имеет приоритет
#[auto]
#[field(response)]
pub created_at: DateTime<Utc>,

#[field(...)]

Управляет включением в DTO. Комбинируйте несколько опций:

#[field(create)] // Только в CreateRequest
#[field(update)] // Только в UpdateRequest
#[field(response)] // Только в Response
#[field(create, response)] // В Create и Response
#[field(create, update, response)] // Во всех трёх
#[field(skip)] // Исключено из всех DTO

create

Включает поле в CreateRequest DTO.

#[field(create)]
pub email: String,
// Генерируется:
pub struct CreateUserRequest {
 pub email: String,
}

update

Включает поле в UpdateRequest DTO.

Важно: Необязательные поля автоматически оборачиваются в Option<T> для частичных обновлений.

#[field(update)]
pub name: String, // Не Option
// Генерируется:
pub struct UpdateUserRequest {
 pub name: Option<String>, // Обёрнуто автоматически
}

response

Включает поле в Response DTO.

#[field(response)]
pub email: String,
// Генерируется:
pub struct UserResponse {
 pub id: Uuid, // Всегда включено (имеет #[id])
 pub email: String, // Включено
}

skip

Исключает поле из всех DTO. Используйте для конфиденциальных данных.

#[field(skip)]
pub password_hash: String,

Важно: skip перекрывает все другие опции поля. Поле будет существовать только в:

  • Оригинальной структуре сущности
  • Структуре Row (для чтения из БД)
  • Структуре Insertable (для записи в БД)

#[column(pg_enum = "...")]

Подключает Postgres-enum (ValueObject) к генерации DDL.

#[derive(ValueObject, Debug, Clone, Serialize, Deserialize)]
#[value_object(pg_type = "order_status", sqlx)]
pub enum OrderStatus { Pending, Shipped, Delivered }
#[derive(Entity)]
#[entity(table = "orders", migrations)]
pub struct Order {
 #[id]
 pub id: Uuid,
 #[field(create, update, response)]
 #[column(pg_enum = "order_status")]
 pub status: OrderStatus,
}
for ddl in Order::MIGRATION_TYPES {
 sqlx::query(ddl).execute(&pool).await?;
}
sqlx::query(Order::MIGRATION_UP).execute(&pool).await?;
  • Задаёт тип колонки в DDL (иначе enum-поля откатываются к TEXT)
  • Регистрирует идемпотентный PG_CREATE_TYPE enum'а в {Entity}::MIGRATION_TYPES — выполняйте их до MIGRATION_UP
  • Указанное имя сверяется с константой PG_TYPE enum'а на этапе компиляции; несовпадение ломает сборку
  • Опциональный флаг sqlx у ValueObject генерирует импелы sqlx::Type / Encode / Decode; не указывайте его, если уже деривите sqlx::Type

#[scope(...)]

Уровень сущности. Объявляет выборку по OR-группе колонок одного типа — «строки, где участник задействован в любой роли».

#[derive(Entity)]
#[entity(table = "disputes")]
#[scope(involving: requester_id | subject_id)]
#[scope(handled: requester_id | subject_id, within = parcel_id)]
pub struct Dispute { /* ... */ }

Генерируется: list_involving(value, limit, offset) и list_handled(parcel_id, value, limit, offset). Значение биндится один раз и сравнивается со всеми объявленными колонками; within сначала сужает группу до одного родителя. Нужны минимум две колонки, они должны существовать и совпадать по типу — всё проверяется на компиляции. Учитывает мягкое удаление, порядок по id по убыванию.

#[owner]

Скоупинг строк по владельцу. Помечает колонку с id владельца; репозиторий получает scoped-методы, которые не раскрывают существование чужих строк и учитывают soft_delete.

#[derive(Entity)]
#[entity(table = "orders")]
pub struct Order {
 #[id]
 pub id: Uuid,
 #[owner]
 pub user_id: Uuid,
 #[field(create, update, response)]
 pub note: String,
}
let mine = pool.list_by_owner(user_id, 20, 0).await?;
let order = pool.find_by_id_scoped(id, user_id).await?;
let updated = pool.update_scoped(id, user_id, patch).await?;
let removed = pool.delete_scoped(id, user_id).await?;

Генерируется: find_by_id_scoped, list_by_owner, update_scoped (при наличии update-полей; None, если строка не принадлежит владельцу), delete_scoped. Максимум одно поле #[owner]; сочетание с #[id] отклоняется на этапе компиляции.

#[filter] / #[filter(...)]

Генерирует поля фильтрации запросов. Подробнее см. Фильтрация.

#[filter] // Точное совпадение: WHERE field = $n
#[filter(eq)] // То же самое
#[filter(like)] // Совпадение по шаблону: WHERE field ILIKE $n
#[filter(range)] // Диапазон: WHERE field >= $n AND field <= $m

#[belongs_to(Entity)]

Связь по внешнему ключу. Подробнее см. Связи.

#[belongs_to(User)]
pub user_id: Uuid,

Генерируется: метод find_user() в репозитории.

#[has_many(Entity)]

Связь один-ко-многим (на уровне сущности). Подробнее см. Связи.

#[has_many(Post)]
pub struct User { /* ... */ }

Генерируется: метод find_posts() в репозитории.

#[schema(...)]

Переопределяет описание поля в OpenAPI. Список токенов передаётся в utoipa без изменений и попадает на каждую сгенерированную структуру с ToSchema — DTO создания и ответа, а также объединённое представление.

Колонка JSONB с типом serde_json::Value документируется как объект произвольной формы; переопределение называет реальную форму:

#[field(create, response)]
#[schema(value_type = Option<SizeCm>)]
pub size_cm: Option<serde_json::Value>,

Без возможности api ни одна сгенерированная структура не выводит ToSchema, поэтому атрибут отбрасывается. DTO обновления его не несут: макрос переписывает типы их полей ради семантики PATCH, и объявленный value_type противоречил бы сгенерированному типу.

#[projection(Name: fields)]

Генерирует структуру частичного представления (на уровне сущности).

#[projection(Public: id, name, avatar)]
#[projection(Admin: id, name, email, role)]
pub struct User { /* ... */ }

Генерируется:

  • UserPublic { id, name, avatar }
  • UserAdmin { id, name, email, role }
  • Реализации From<User>
  • Методы find_by_id_public(), find_by_id_admin()

Атрибуты команд

Применяются на уровне сущности с помощью #[command(...)].

Краткий справочник

Синтаксис Эффект
#[command(Name)] Использует все поля #[field(create)]
#[command(Name: field1, field2)] Использует только указанные поля (добавляет requires_id)
#[command(Name, requires_id)] Добавляет поле ID, без других полей
#[command(Name, source = "create")] Явно использовать create-поля (по умолчанию)
#[command(Name, source = "update")] Использовать update-поля (опциональные, добавляет requires_id)
#[command(Name, source = "none")] Без полей payload
#[command(Name, payload = "Type")] Использует пользовательскую структуру payload
#[command(Name, result = "Type")] Использует пользовательский тип результата
#[command(Name, kind = "create")] Подсказка: создаёт сущность (по умолчанию)
#[command(Name, kind = "update")] Подсказка: изменяет сущность
#[command(Name, kind = "delete")] Подсказка: удаляет сущность (возвращает ())
#[command(Name, kind = "custom")] Подсказка: пользовательская операция

Подробнее см. Команды.

Полный пример

#[derive(Entity)]
#[entity(
 table = "posts",
 schema = "blog",
 sql = "full",
 dialect = "postgres",
 uuid = "v7",
 soft_delete,
 returning = "full",
 events,
 hooks,
 commands
)]
#[has_many(Comment)]
#[projection(Summary: id, title, author_id, created_at)]
#[command(Publish)]
#[command(Archive, requires_id)]
pub struct Post {
 #[id]
 pub id: Uuid,
 #[field(create, update, response)]
 #[filter(like)]
 pub title: String,
 #[field(create, update, response)]
 pub content: String,
 #[field(create, response)]
 #[belongs_to(User)]
 #[filter]
 pub author_id: Uuid,
 #[field(update, response)]
 pub published: bool,
 #[field(response)]
 #[filter(range)]
 pub view_count: i64,
 #[field(skip)]
 pub moderation_notes: String,
 #[field(skip)]
 pub deleted_at: Option<DateTime<Utc>>,
 #[auto]
 #[field(response)]
 #[filter(range)]
 pub created_at: DateTime<Utc>,
 #[auto]
 #[field(response)]
 pub updated_at: DateTime<Utc>,
}

Матрица решений

Я хочу... Атрибуты
Автогенерировать первичный ключ #[id]
Использовать случайный UUID uuid = "v4" на сущности
Использовать упорядоченный по времени UUID uuid = "v7" (по умолчанию)
Принимать в теле POST #[field(create)]
Принимать в теле PATCH #[field(update)]
Возвращать в ответе API #[field(response)]
Принимать и возвращать #[field(create, update, response)]
Скрыть от всех API #[field(skip)]
Автогенерировать timestamp #[auto] + #[field(response)]
Только для чтения (управляет БД) только #[field(response)]
Только для записи (без возврата) только #[field(create)]
Пользовательские SQL-запросы sql = "trait"
Только DTO, без БД sql = "none"
Мягкое удаление записей soft_delete на сущности
Пользовательский тип ошибки error = "MyError" на сущности
Фильтровать по точному значению #[filter] на поле
Фильтровать по шаблону #[filter(like)] на поле
Фильтровать по диапазону #[filter(range)] на поле
Отслеживать изменения сущности events на сущности
Выполнять код при жизненном цикле hooks на сущности
Использовать доменные команды commands на сущности + #[command(...)]
Использовать транзакции с несколькими сущностями transactions на сущности
Определить связь #[belongs_to(Entity)] или #[has_many(Entity)]
Частичное представление сущности #[projection(Name: fields)]
Переопределить тип поля в OpenAPI #[schema(value_type = T)]

Update DTO: семантика PATCH

Генерируемые обновления — настоящие частичные патчи: SET-клауза строится в рантайме из реально присутствующих полей, пропущенные поля не трогаются. Nullable-колонки используют двойной Option (None = не менять, Some(None) = записать NULL, Some(Some(v)) = записать v) через entity_core::serde_helpers::double_option.

Цепочечные сеттеры выражают тот же патч без вложенности: set_{field} для каждой обновляемой колонки, clear_{field} для nullable и expecting_version, если объявлен #[version].

let patch = UpdateUserRequest::default()
 .set_name("Neo".into())
 .clear_nickname();

Конструирование через структурный литерал продолжает работать — сеттеры только дополняют его.

// {} → nothing changes
// {"nickname": null} → nickname = NULL
// {"nickname": "neo"} → nickname = 'neo'
let patch: UpdateProfileRequest = serde_json::from_str(body)?;
let profile = pool.update(id, patch).await?;

Опции migrations(...)

Помимо простого флага, migrations принимает DDL-опции: touch_updated_at (общая plpgsql-функция + BEFORE UPDATE триггер на таблицу, обновляющий updated_at; требует поля updated_at, проверяется на компиляции), audit (таблица entity_audit_log + триггер с диффами to_jsonb(OLD/NEW)) и extensions = "pg_trgm, pgcrypto" (идемпотентные CREATE EXTENSION). Новые константы: MIGRATION_TRIGGERS (выполнять после MIGRATION_UP) и MIGRATION_EXTENSIONS (до).

#[entity(table = "articles", migrations(touch_updated_at, audit, extensions = "pg_trgm"))]
pub struct Article { /* ... */ }
for ddl in Article::MIGRATION_EXTENSIONS { sqlx::query(ddl).execute(&pool).await?; }
sqlx::query(Article::MIGRATION_UP).execute(&pool).await?;
for ddl in Article::MIGRATION_TRIGGERS { sqlx::query(ddl).execute(&pool).await?; }

#[version]

Оптимистичная блокировка. Помечает целочисленную колонку (i16/i32/i64); Update DTO получает обязательное поле expected_version, генерируемый UPDATE инкрементирует колонку и применяется только пока версия в базе совпадает — устаревшая запись падает с ошибкой конфликта вместо перезаписи свежих данных. DDL по умолчанию INTEGER NOT NULL DEFAULT 0. Работает в обычном, scoped и транзакционном update.

#[derive(Entity)]
#[entity(table = "orders", migrations)]
pub struct Order {
 #[id] pub id: Uuid,
 #[field(create, update, response)] pub note: String,
 #[version] #[field(response)] #[auto] pub version: i32,
}
let patch = UpdateOrderRequest { note: Some("v2".into()), expected_version: order.version };
let updated = pool.update(order.id, patch).await?;

typed_constraints (опциональный)

Макрос знает каждый constraint, который создаёт. С этим флагом генерируемые write-методы резолвят имена нарушенных constraint'ов (unique-колонки, внешние ключи belongs_to, check'и колонок, имена unique_index) и отдают entity_core::ConstraintError { kind, constraint, field } вместо сырой ошибки драйвера. Требует кастомного типа error с From<ConstraintError>; без флага поведение не меняется.

#[entity(table = "users", typed_constraints, error = "AppError")]
pub struct User {
 #[id] pub id: Uuid,
 #[field(create, response)] #[column(unique)] pub email: String,
}
match pool.create(dto).await {
 Err(AppError::Constraint(v)) if v.field == Some("email") => conflict_409(),
 other => other?,
}

#[embed(prefix = "...", fields(...))]

Разворачивает value object в плоские колонки с префиксом. DDL, Row-структура, CRUD SQL и динамический PATCH работают с price_amount_cents / price_currency, а DTO и сущность несут саму структуру. Объявленная форма деструктурируется против реальной структуры на этапе компиляции — переименованное, перетипизированное, отсутствующее или лишнее поле ломает сборку. Родители Option<T> пока не поддерживаются.

pub struct Money { pub amount_cents: i64, pub currency: String }
#[derive(Entity)]
#[entity(table = "products", migrations)]
pub struct Product {
 #[id] pub id: Uuid,
 #[field(create, update, response)]
 #[embed(prefix = "price_", fields(amount_cents: i64, currency: String))]
 pub price: Money,
}

Фича garde (бэкенд валидации)

Включает garde::Validate на генерируемых DTO как поддерживаемую альтернативу validator. Правила #[validate(...)] (length, range, email, url, pattern) транслируются в синтаксис garde; поля без ограничений получают garde(skip); Option-обёртки Update DTO валидируют внутреннее значение через inner(...). При включении обеих фич приоритет у validate.

#[field(create, update, response)]
#[validate(length(min = 3, max = 8))]
pub name: String,
let dto: CreateUserRequest = serde_json::from_str(body)?;
garde::Validate::validate(&dto)?;

constraint(...) (опциональный, вместе с typed_constraints)

Объявляет constraint'ы, которые макрос не может вывести — внешние ключи по натуральным ключам, CHECK'и с кастомными именами, индексы из ручных миграций — чтобы нарушения резолвились в ConstraintError с указанным полем. Виды: unique, foreign_key, check. Кастомные записи имеют приоритет над выведенными с тем же именем. Требует typed_constraints.

#[entity(
 table = "orders",
 typed_constraints,
 constraint(name = "orders_currency_fkey", kind = "foreign_key", field = "currency"),
 constraint(name = "orders_window_check", kind = "check"),
)]

Транзакционный upsert

При включённых transactions и upsert(...) адаптер {Entity}TransactionRepo получает upsert с той же SQL-семантикой, что и метод пула, но на хендле транзакции — для потоков, где upsert должен быть атомарен с соседними стейтментами.

let mut tx = pool.begin().await?;
sqlx::query("UPDATE users SET username = NULL WHERE ...").execute(&mut *tx).await?;
let user = UserTransactionRepo::new(&mut tx).upsert(dto).await?;
tx.commit().await?;

🌐 Language

🇬🇧 English | 🇷🇺 Русский | 🇰🇷 한국어 | 🇪🇸 Español | 🇨🇳 中文


🇬🇧 English

Home

Getting Started

Features

Advanced


🇷🇺 Русский

Главная

Начало работы

Возможности

Продвинутое


🇰🇷 한국어

시작하기

기능

고급


🇪🇸 Español

Inicio

Comenzando

Características

Avanzado


🇨🇳 中文

首页

入门

功能

高级


📚 Reference

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /