entity_derive/
lib.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4#![doc = include_str!("../README.md")]
5#![doc(
6    html_logo_url = "https://raw.githubusercontent.com/RAprogramm/entity-derive/main/assets/logo.svg",
7    html_favicon_url = "https://raw.githubusercontent.com/RAprogramm/entity-derive/main/assets/favicon.ico"
8)]
9#![cfg_attr(docsrs, feature(doc_cfg))]
10#![warn(
11    missing_docs,
12    rustdoc::missing_crate_level_docs,
13    rustdoc::broken_intra_doc_links,
14    rust_2018_idioms
15)]
16#![deny(unsafe_code)]
17
18//! # Quick Navigation
19//!
20//! - **Getting Started**: See the [crate documentation](crate) above
21//! - **Derive Macro**: [`Entity`](macro@Entity) — the main derive macro
22//! - **Examples**: Check the [examples directory](https://github.com/RAprogramm/entity-derive/tree/main/examples)
23//! - **Wiki**: [Comprehensive guides](https://github.com/RAprogramm/entity-derive/wiki)
24//!
25//! # Attribute Quick Reference
26//!
27//! ## Entity-Level `#[entity(...)]`
28//!
29//! ```rust,ignore
30//! #[derive(Entity)]
31//! #[entity(
32//!     table = "users",      // Required: database table name
33//!     schema = "public",    // Optional: database schema (default: "public")
34//!     sql = "full"          // Optional: "full" | "trait" | "none" (default: "full")
35//! )]
36//! pub struct User { /* ... */ }
37//! ```
38//!
39//! ## Field-Level Attributes
40//!
41//! ```rust,ignore
42//! pub struct User {
43//!     #[id]                           // Primary key, UUID v7, always in response
44//!     pub id: Uuid,
45//!
46//!     #[field(create, update, response)]  // In all DTOs
47//!     pub name: String,
48//!
49//!     #[field(create, response)]      // Create + Response only
50//!     pub email: String,
51//!
52//!     #[field(skip)]                  // Excluded from all DTOs
53//!     pub password_hash: String,
54//!
55//!     #[field(response)]
56//!     #[auto]                         // Auto-generated (excluded from create/update)
57//!     pub created_at: DateTime<Utc>,
58//! }
59//! ```
60//!
61//! # Generated Code Overview
62//!
63//! For a `User` entity, the macro generates:
64//!
65//! | Generated Type | Description |
66//! |----------------|-------------|
67//! | `CreateUserRequest` | DTO for `POST` requests |
68//! | `UpdateUserRequest` | DTO for `PATCH` requests (all fields `Option<T>`) |
69//! | `UserResponse` | DTO for API responses |
70//! | `UserRow` | Database row mapping (for `sqlx::FromRow`) |
71//! | `InsertableUser` | Struct for `INSERT` statements |
72//! | `UserRepository` | Async trait with CRUD methods |
73//! | `impl UserRepository for PgPool` | PostgreSQL implementation |
74//! | `From<...>` impls | Type conversions between all structs |
75//!
76//! # SQL Generation Modes
77//!
78//! | Mode | Generates Trait | Generates Impl | Use Case |
79//! |------|-----------------|----------------|----------|
80//! | `sql = "full"` | ✅ | ✅ | Standard CRUD, simple queries |
81//! | `sql = "trait"` | ✅ | ❌ | Custom SQL (joins, CTEs, search) |
82//! | `sql = "none"` | ❌ | ❌ | DTOs only, no database layer |
83//!
84//! # Repository Methods
85//!
86//! The generated `{Name}Repository` trait includes:
87//!
88//! ```rust,ignore
89//! #[async_trait]
90//! pub trait UserRepository: Send + Sync {
91//!     type Error: std::error::Error + Send + Sync;
92//!
93//!     /// Create a new entity
94//!     async fn create(&self, dto: CreateUserRequest) -> Result<User, Self::Error>;
95//!
96//!     /// Find entity by primary key
97//!     async fn find_by_id(&self, id: Uuid) -> Result<Option<User>, Self::Error>;
98//!
99//!     /// Update entity with partial data
100//!     async fn update(&self, id: Uuid, dto: UpdateUserRequest) -> Result<User, Self::Error>;
101//!
102//!     /// Delete entity by primary key
103//!     async fn delete(&self, id: Uuid) -> Result<bool, Self::Error>;
104//!
105//!     /// List entities with pagination
106//!     async fn list(&self, limit: i64, offset: i64) -> Result<Vec<User>, Self::Error>;
107//! }
108//! ```
109//!
110//! # Error Handling
111//!
112//! The generated implementation uses `sqlx::Error` as the error type.
113//! You can wrap it in your application's error type:
114//!
115//! ```rust,ignore
116//! use entity_derive::Entity;
117//!
118//! #[derive(Entity)]
119//! #[entity(table = "users", sql = "trait")]  // Generate trait only
120//! pub struct User { /* ... */ }
121//!
122//! // Implement with your own error type
123//! #[async_trait]
124//! impl UserRepository for MyDatabase {
125//!     type Error = MyAppError;  // Your custom error
126//!
127//!     async fn create(&self, dto: CreateUserRequest) -> Result<User, Self::Error> {
128//!         // Your implementation
129//!     }
130//! }
131//! ```
132//!
133//! # Compile-Time Guarantees
134//!
135//! This crate provides several compile-time guarantees:
136//!
137//! - **No sensitive data leaks**: Fields marked `#[field(skip)]` are excluded
138//!   from all DTOs
139//! - **Type-safe updates**: `UpdateRequest` fields are properly wrapped in
140//!   `Option`
141//! - **Consistent mapping**: `From` impls are always in sync with field
142//!   definitions
143//! - **SQL injection prevention**: All queries use parameterized bindings
144//!
145//! # Performance
146//!
147//! - **Zero runtime overhead**: All code generation happens at compile time
148//! - **No reflection**: Generated code is plain Rust structs and impls
149//! - **Minimal dependencies**: Only proc-macro essentials (syn, quote, darling)
150//!
151//! # Comparison with Alternatives
152//!
153//! | Feature | entity-derive | Diesel | SeaORM |
154//! |---------|---------------|--------|--------|
155//! | DTO generation | ✅ | ❌ | ❌ |
156//! | Repository pattern | ✅ | ❌ | Partial |
157//! | Type-safe SQL | ✅ | ✅ | ✅ |
158//! | Async support | ✅ | Partial | ✅ |
159//! | Boilerplate reduction | ~90% | ~50% | ~60% |
160
161mod entity;
162mod utils;
163
164use proc_macro::TokenStream;
165
166/// Derive macro for generating complete domain boilerplate from a single entity
167/// definition.
168///
169/// # Overview
170///
171/// The `Entity` derive macro generates all the boilerplate code needed for a
172/// typical CRUD application: DTOs, repository traits, SQL implementations, and
173/// type mappers.
174///
175/// # Generated Types
176///
177/// For an entity named `User`, the macro generates:
178///
179/// - **`CreateUserRequest`** — DTO for creation (fields marked with
180///   `#[field(create)]`)
181/// - **`UpdateUserRequest`** — DTO for updates (fields marked with
182///   `#[field(update)]`, wrapped in `Option`)
183/// - **`UserResponse`** — DTO for responses (fields marked with
184///   `#[field(response)]`)
185/// - **`UserRow`** — Database row struct (implements `sqlx::FromRow`)
186/// - **`InsertableUser`** — Struct for INSERT operations
187/// - **`UserRepository`** — Async trait with CRUD methods
188/// - **`impl UserRepository for PgPool`** — PostgreSQL implementation (when
189///   `sql = "full"`)
190///
191/// # Entity Attributes
192///
193/// Configure the entity using `#[entity(...)]`:
194///
195/// | Attribute | Required | Default | Description |
196/// |-----------|----------|---------|-------------|
197/// | `table` | **Yes** | — | Database table name |
198/// | `schema` | No | `"public"` | Database schema name |
199/// | `sql` | No | `"full"` | SQL generation: `"full"`, `"trait"`, or `"none"` |
200///
201/// # Field Attributes
202///
203/// | Attribute | Description |
204/// |-----------|-------------|
205/// | `#[id]` | Primary key. Auto-generates UUID v7. Always included in `Response`. |
206/// | `#[auto]` | Auto-generated field (e.g., `created_at`). Excluded from `Create`/`Update`. |
207/// | `#[field(create)]` | Include in `CreateRequest`. |
208/// | `#[field(update)]` | Include in `UpdateRequest`. Wrapped in `Option<T>` if not already. |
209/// | `#[field(response)]` | Include in `Response`. |
210/// | `#[field(skip)]` | Exclude from ALL DTOs. Use for sensitive data. |
211///
212/// Multiple attributes can be combined: `#[field(create, update, response)]`
213///
214/// # Examples
215///
216/// ## Basic Usage
217///
218/// ```rust,ignore
219/// use entity_derive::Entity;
220/// use uuid::Uuid;
221/// use chrono::{DateTime, Utc};
222///
223/// #[derive(Entity)]
224/// #[entity(table = "users", schema = "core")]
225/// pub struct User {
226///     #[id]
227///     pub id: Uuid,
228///
229///     #[field(create, update, response)]
230///     pub name: String,
231///
232///     #[field(create, update, response)]
233///     pub email: String,
234///
235///     #[field(skip)]
236///     pub password_hash: String,
237///
238///     #[field(response)]
239///     #[auto]
240///     pub created_at: DateTime<Utc>,
241/// }
242/// ```
243///
244/// ## Custom SQL Implementation
245///
246/// For complex queries with joins, use `sql = "trait"`:
247///
248/// ```rust,ignore
249/// #[derive(Entity)]
250/// #[entity(table = "posts", sql = "trait")]
251/// pub struct Post {
252///     #[id]
253///     pub id: Uuid,
254///     #[field(create, update, response)]
255///     pub title: String,
256///     #[field(create, response)]
257///     pub author_id: Uuid,
258/// }
259///
260/// // Implement the repository yourself
261/// #[async_trait]
262/// impl PostRepository for PgPool {
263///     type Error = sqlx::Error;
264///
265///     async fn find_by_id(&self, id: Uuid) -> Result<Option<Post>, Self::Error> {
266///         sqlx::query_as!(Post,
267///             r#"SELECT p.*, u.name as author_name
268///                FROM posts p
269///                JOIN users u ON p.author_id = u.id
270///                WHERE p.id = $1"#,
271///             id
272///         )
273///         .fetch_optional(self)
274///         .await
275///     }
276///     // ... other methods
277/// }
278/// ```
279///
280/// ## DTOs Only (No Database Layer)
281///
282/// ```rust,ignore
283/// #[derive(Entity)]
284/// #[entity(table = "events", sql = "none")]
285/// pub struct Event {
286///     #[id]
287///     pub id: Uuid,
288///     #[field(create, response)]
289///     pub name: String,
290/// }
291/// // Only generates CreateEventRequest, EventResponse, etc.
292/// // No repository trait or SQL implementation
293/// ```
294///
295/// # Security
296///
297/// Use `#[field(skip)]` to prevent sensitive data from leaking:
298///
299/// ```rust,ignore
300/// pub struct User {
301///     #[field(skip)]
302///     pub password_hash: String,  // Never in any DTO
303///
304///     #[field(skip)]
305///     pub api_secret: String,     // Never in any DTO
306///
307///     #[field(skip)]
308///     pub internal_notes: String, // Admin-only, not in public API
309/// }
310/// ```
311///
312/// # Generated SQL
313///
314/// The macro generates parameterized SQL queries that are safe from injection:
315///
316/// ```sql
317/// -- CREATE
318/// INSERT INTO schema.table (id, field1, field2, ...)
319/// VALUES ($1, $2, $3, ...)
320///
321/// -- READ
322/// SELECT * FROM schema.table WHERE id = $1
323///
324/// -- UPDATE (dynamic based on provided fields)
325/// UPDATE schema.table SET field1 = $1, field2 = $2 WHERE id = $3
326///
327/// -- DELETE
328/// DELETE FROM schema.table WHERE id = $1 RETURNING id
329///
330/// -- LIST
331/// SELECT * FROM schema.table ORDER BY created_at DESC LIMIT $1 OFFSET $2
332/// ```
333#[proc_macro_derive(Entity, attributes(entity, field, id, auto, validate))]
334pub fn derive_entity(input: TokenStream) -> TokenStream {
335    entity::derive(input)
336}