#[derive(Entity)]
{
// Attributes available to this derive:
#[entity]
#[field]
#[id]
#[auto]
#[validate]
#[belongs_to]
#[has_many]
#[projection]
#[filter]
#[command]
#[example]
#[column]
}
Expand description
Derive macro for generating complete domain boilerplate from a single entity definition.
§Overview
The Entity derive macro generates all the boilerplate code needed for a
typical CRUD application: DTOs, repository traits, SQL implementations, and
type mappers.
§Generated Types
For an entity named User, the macro generates:
CreateUserRequest— DTO for creation (fields marked with#[field(create)])UpdateUserRequest— DTO for updates (fields marked with#[field(update)], wrapped inOption)UserResponse— DTO for responses (fields marked with#[field(response)])UserRow— Database row struct (implementssqlx::FromRow)InsertableUser— Struct for INSERT operationsUserRepository— Async trait with CRUD methodsimpl UserRepository for PgPool— PostgreSQL implementation (whensql = "full")
§Entity Attributes
Configure the entity using #[entity(...)]:
| Attribute | Required | Default | Description |
|---|---|---|---|
table | Yes | — | Database table name |
schema | No | "public" | Database schema name |
sql | No | "full" | SQL generation: "full", "trait", or "none" |
dialect | No | "postgres" | Database dialect: "postgres", "clickhouse", "mongodb" |
uuid | No | "v7" | UUID version for ID: "v7" (time-ordered) or "v4" (random) |
migrations | No | false | Generate MIGRATION_UP and MIGRATION_DOWN constants |
§Field Attributes
| Attribute | Description |
|---|---|
#[id] | Primary key. Auto-generates UUID (v7 by default, configurable with uuid attribute). Always included in Response. |
#[auto] | Auto-generated field (e.g., created_at). Excluded from Create/Update. |
#[field(create)] | Include in CreateRequest. |
#[field(update)] | Include in UpdateRequest. Wrapped in Option<T> if not already. |
#[field(response)] | Include in Response. |
#[field(skip)] | Exclude from ALL DTOs. Use for sensitive data. |
#[belongs_to(Entity)] | Foreign key relation. Generates find_{entity} method in repository. |
#[belongs_to(Entity, on_delete = "...")] | Foreign key with ON DELETE action (cascade, set null, restrict). |
#[has_many(Entity)] | One-to-many relation (entity-level). Generates find_{entities} method. |
#[projection(Name: f1, f2)] | Entity-level. Defines a projection struct with specified fields. |
#[filter] | Exact match filter. Generates field in Query struct with = comparison. |
#[filter(like)] | ILIKE pattern filter. Generates field for text pattern matching. |
#[filter(range)] | Range filter. Generates field_from and field_to fields. |
#[column(unique)] | Add UNIQUE constraint in migrations. |
#[column(index)] | Add btree index in migrations. |
#[column(index = "gin")] | Add index with specific type (btree, hash, gin, gist, brin). |
#[column(default = "...")] | Set DEFAULT value in migrations. |
#[column(check = "...")] | Add CHECK constraint in migrations. |
#[column(varchar = N)] | Use VARCHAR(N) instead of TEXT in migrations. |
Multiple attributes can be combined: #[field(create, update, response)]
§Examples
§Basic Usage
ⓘ
use entity_derive::Entity;
use uuid::Uuid;
use chrono::{DateTime, Utc};
#[derive(Entity)]
#[entity(table = "users", schema = "core")]
pub struct User {
#[id]
pub id: Uuid,
#[field(create, update, response)]
pub name: String,
#[field(create, update, response)]
pub email: String,
#[field(skip)]
pub password_hash: String,
#[field(response)]
#[auto]
pub created_at: DateTime<Utc>,
}§Custom SQL Implementation
For complex queries with joins, use sql = "trait":
ⓘ
#[derive(Entity)]
#[entity(table = "posts", sql = "trait")]
pub struct Post {
#[id]
pub id: Uuid,
#[field(create, update, response)]
pub title: String,
#[field(create, response)]
pub author_id: Uuid,
}
// Implement the repository yourself
#[async_trait]
impl PostRepository for PgPool {
type Error = sqlx::Error;
async fn find_by_id(&self, id: Uuid) -> Result<Option<Post>, Self::Error> {
sqlx::query_as!(Post,
r#"SELECT p.*, u.name as author_name
FROM posts p
JOIN users u ON p.author_id = u.id
WHERE p.id = $1"#,
id
)
.fetch_optional(self)
.await
}
// ... other methods
}§DTOs Only (No Database Layer)
ⓘ
#[derive(Entity)]
#[entity(table = "events", sql = "none")]
pub struct Event {
#[id]
pub id: Uuid,
#[field(create, response)]
pub name: String,
}
// Only generates CreateEventRequest, EventResponse, etc.
// No repository trait or SQL implementation§Migration Generation
Generate compile-time SQL migrations with migrations:
ⓘ
#[derive(Entity)]
#[entity(table = "products", migrations)]
pub struct Product {
#[id]
pub id: Uuid,
#[field(create, update, response)]
#[column(unique, index)]
pub sku: String,
#[field(create, update, response)]
#[column(varchar = 200)]
pub name: String,
#[field(create, update, response)]
#[column(check = "price >= 0")]
pub price: f64,
#[belongs_to(Category, on_delete = "cascade")]
pub category_id: Uuid,
}
// Generated constants:
// Product::MIGRATION_UP - CREATE TABLE, indexes, constraints
// Product::MIGRATION_DOWN - DROP TABLE CASCADE
// Apply migration:
sqlx::query(Product::MIGRATION_UP).execute(&pool).await?;§Security
Use #[field(skip)] to prevent sensitive data from leaking:
ⓘ
pub struct User {
#[field(skip)]
pub password_hash: String, // Never in any DTO
#[field(skip)]
pub api_secret: String, // Never in any DTO
#[field(skip)]
pub internal_notes: String, // Admin-only, not in public API
}§Generated SQL
The macro generates parameterized SQL queries that are safe from injection:
-- CREATE
INSERT INTO schema.table (id, field1, field2, ...)
VALUES ($1, $2, $3, ...)
-- READ
SELECT * FROM schema.table WHERE id = $1
-- UPDATE (dynamic based on provided fields)
UPDATE schema.table SET field1 = $1, field2 = $2 WHERE id = $3
-- DELETE
DELETE FROM schema.table WHERE id = $1 RETURNING id
-- LIST
SELECT * FROM schema.table ORDER BY created_at DESC LIMIT $1 OFFSET $2