Skip to content

RAprogramm/entity-derive

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

221 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

entity-derive logo

entity-derive

One macro to rule them all

Generate DTOs, repositories, mappers, and SQL from a single entity definition

Crates.io Documentation CI Status

Coverage License: MIT REUSE Compliant Wiki


The Problem

Building a typical CRUD application requires writing the same boilerplate over and over: entity struct, create DTO, update DTO, response DTO, row struct, repository trait, SQL implementation, and 6+ From implementations.

That's 200+ lines of boilerplate for a single entity.

The Solution

#[derive(Entity)]
#[entity(table = "users")]
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>,
}

Done. The macro generates everything else.


Installation

[dependencies]
entity-derive = { version = "0.8", features = ["postgres", "api"] }

Feature flags

Feature Default What it does
postgres βœ“ Generate sqlx::PgPool-backed repository implementations
events βœ“ Generate {Entity}Event enum (Created / Updated / Deleted variants)
commands βœ“ CQRS command pattern: command structs + dispatcher (#[entity(commands)], #[command(...)])
hooks βœ“ {Entity}Hooks trait with before/after lifecycle methods
transactions βœ“ {Entity}TransactionRepo adapter + transaction builder helpers (#[entity(transactions)])
aggregate_root βœ“ New{Entity} constructor type and transactional save() (#[entity(aggregate_root)])
migrations βœ“ Compile-time MIGRATION_UP / MIGRATION_DOWN SQL constants (#[entity(migrations)])
projections βœ“ Projection structs and find_by_id_<projection> lookups (#[projection(...)])
clickhouse Generate ClickHouse-backed repositories (planned)
mongodb Generate MongoDB-backed repositories (planned)
streams {Entity}Subscriber using Postgres LISTEN/NOTIFY (pulls in events)
api Generate HTTP handlers (axum) and utoipa OpenAPI schemas
validate Wire up validator::Validate on generated DTOs
tracing Wrap every generated async method in #[tracing::instrument] carrying entity + op span fields

Default features cover the full entity-attribute surface so existing projects work without changes. For lean builds, opt out of what you don't need:

[dependencies]
# Just repositories β€” no events, hooks, commands, etc.
entity-derive = { version = "0.8", default-features = false, features = ["postgres"] }

If you use an entity attribute whose feature is disabled (e.g. #[entity(commands)] without features = ["commands"]), the macro emits a compile_error! at the attribute pointing to the missing feature.

Enable extras alongside the defaults:

[dependencies]
entity-derive = { version = "0.8", features = ["postgres", "api", "tracing", "streams"] }
tracing = "0.1"
tracing-subscriber = "0.3"

Features

Feature Description
Zero Runtime Cost All code generation at compile time
Type Safe Change a field once, everything updates
Auto HTTP Handlers api(handlers) generates CRUD endpoints + router
OpenAPI Docs Auto-generated Swagger/OpenAPI documentation
Query Filtering Type-safe #[filter], #[filter(like)], #[filter(range)]
Relations #[belongs_to] and #[has_many]
Aggregate Roots #[entity(aggregate_root)] with New{T} DTOs and transactional save
Transactions Multi-entity atomic operations
Lifecycle Events Created, Updated, Deleted events
Real-Time Streams Postgres LISTEN/NOTIFY integration
Lifecycle Hook Traits {Entity}Hooks trait emitted with before_create / after_update / etc.; invocation is currently manual at your service layer (tracking auto-invocation: #127)
CQRS Commands Business-oriented command pattern
Soft Delete deleted_at timestamp support
Structured Logging Opt-in tracing feature wraps every generated async method in #[tracing::instrument] with entity + op fields

Documentation

Topic Languages
Getting Started
Attributes πŸ‡¬πŸ‡§ πŸ‡·πŸ‡Ί πŸ‡°πŸ‡· πŸ‡ͺπŸ‡Έ πŸ‡¨πŸ‡³
Examples πŸ‡¬πŸ‡§ πŸ‡·πŸ‡Ί πŸ‡°πŸ‡· πŸ‡ͺπŸ‡Έ πŸ‡¨πŸ‡³
Features
Filtering πŸ‡¬πŸ‡§ πŸ‡·πŸ‡Ί πŸ‡°πŸ‡· πŸ‡ͺπŸ‡Έ πŸ‡¨πŸ‡³
Relations πŸ‡¬πŸ‡§ πŸ‡·πŸ‡Ί πŸ‡°πŸ‡· πŸ‡ͺπŸ‡Έ πŸ‡¨πŸ‡³
Events πŸ‡¬πŸ‡§ πŸ‡·πŸ‡Ί πŸ‡°πŸ‡· πŸ‡ͺπŸ‡Έ πŸ‡¨πŸ‡³
Streams πŸ‡¬πŸ‡§ πŸ‡·πŸ‡Ί πŸ‡°πŸ‡· πŸ‡ͺπŸ‡Έ πŸ‡¨πŸ‡³
Hooks πŸ‡¬πŸ‡§ πŸ‡·πŸ‡Ί πŸ‡°πŸ‡· πŸ‡ͺπŸ‡Έ πŸ‡¨πŸ‡³
Commands πŸ‡¬πŸ‡§ πŸ‡·πŸ‡Ί πŸ‡°πŸ‡· πŸ‡ͺπŸ‡Έ πŸ‡¨πŸ‡³
Advanced
Custom SQL πŸ‡¬πŸ‡§ πŸ‡·πŸ‡Ί πŸ‡°πŸ‡· πŸ‡ͺπŸ‡Έ πŸ‡¨πŸ‡³
Web Frameworks πŸ‡¬πŸ‡§ πŸ‡·πŸ‡Ί πŸ‡°πŸ‡· πŸ‡ͺπŸ‡Έ πŸ‡¨πŸ‡³
Best Practices πŸ‡¬πŸ‡§ πŸ‡·πŸ‡Ί πŸ‡°πŸ‡· πŸ‡ͺπŸ‡Έ πŸ‡¨πŸ‡³

Quick Reference

Entity Attributes

#[entity(
    table = "users",           // Required: table name
    schema = "public",         // Optional: schema (default: omitted)
    dialect = "postgres",      // Optional: database dialect
    aggregate_root,            // Optional: New{T} DTOs + transactional save
    soft_delete,               // Optional: use deleted_at instead of DELETE
    events,                    // Optional: generate lifecycle events
    streams,                   // Optional: real-time Postgres NOTIFY
    hooks,                     // Optional: before/after lifecycle hooks
    commands,                  // Optional: CQRS command pattern
    transactions,              // Optional: multi-entity transaction support
    api(                       // Optional: generate HTTP handlers + OpenAPI
        tag = "Users",
        handlers,              // All CRUD, or handlers(get, list, create)
        security = "bearer",   // cookie, bearer, api_key, or none
        title = "My API",
        api_version = "1.0.0",
    ),
)]

Field Attributes

#[id]                          // Primary key (auto-generated UUID)
#[auto]                        // Auto-generated (timestamps)
#[field(create)]               // Include in CreateRequest
#[field(update)]               // Include in UpdateRequest
#[field(response)]             // Include in Response
#[field(skip)]                 // Exclude from all DTOs
#[filter]                      // Exact match filter
#[filter(like)]                // ILIKE pattern filter
#[filter(range)]               // Range filter (from/to)
#[belongs_to(Entity)]          // Foreign key relation
#[has_many(Entity)]            // One-to-many relation
#[projection(Name: fields)]    // Partial view

Transactions

Mark each participating entity with #[entity(table = "…", transactions)] and drive a multi-entity transaction through Transaction::run. The closure receives &mut TransactionContext; run commits on Ok and rolls back on Err (or any panic) automatically:

use entity_core::transaction::Transaction;

Transaction::new(&pool)
    .run(async |ctx| {
        let user = ctx.users().create(create_user).await?;
        ctx.orders().create(order_for(user.id)).await?;
        Ok::<_, sqlx::Error>(user)
    })
    .await?;

Need conditional commit/rollback inside the closure? Use run_with_commit β€” it takes TransactionContext by value so the closure can call ctx.commit().await (or ctx.rollback().await) itself.

Tracing

Opt-in with the tracing feature. Every generated async method (create, find_by_id, update, delete, list, find_by_<field>, projections, transaction adapters, stream subscribers) is wrapped in #[tracing::instrument(skip_all, fields(entity, op), err(Debug))].

entity-derive = { version = "0.8", features = ["postgres", "tracing"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

With a subscriber initialized, a failed User::create surfaces as:

ERROR entity.User.create: error=database error: duplicate key value violates unique constraint
  in entity.User.create with entity="User" op="create"

When the feature is off, generated code is byte-for-byte identical to a build without the attribute β€” zero runtime cost.


Code Coverage

Coverage Sunburst

About

Derive macro that generates DTOs, repositories, SQL queries, REST handlers, and OpenAPI docs from a single Rust struct definition

Resources

License

Contributing

Stars

Watchers

Forks

Contributors

Languages