Entity

Derive Macro Entity 

Source
#[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 in Option)
  • UserResponse — DTO for responses (fields marked with #[field(response)])
  • UserRow — Database row struct (implements sqlx::FromRow)
  • InsertableUser — Struct for INSERT operations
  • UserRepository — Async trait with CRUD methods
  • impl UserRepository for PgPool — PostgreSQL implementation (when sql = "full")

§Entity Attributes

Configure the entity using #[entity(...)]:

AttributeRequiredDefaultDescription
tableYesDatabase table name
schemaNo"public"Database schema name
sqlNo"full"SQL generation: "full", "trait", or "none"
dialectNo"postgres"Database dialect: "postgres", "clickhouse", "mongodb"
uuidNo"v7"UUID version for ID: "v7" (time-ordered) or "v4" (random)
migrationsNofalseGenerate MIGRATION_UP and MIGRATION_DOWN constants

§Field Attributes

AttributeDescription
#[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