entity_core/
lib.rs

1// SPDX-FileCopyrightText: 2025-2026 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4//! Core traits and types for entity-derive.
5//!
6//! This crate provides the foundational traits and types used by entity-derive
7//! generated code. It can also be used standalone for manual implementations.
8//!
9//! # Overview
10//!
11//! - [`Repository`] — Base trait for all generated repository traits
12//! - [`Pagination`] — Common pagination parameters
13//! - [`prelude`] — Convenient re-exports
14//!
15//! # Usage
16//!
17//! Most users should use `entity-derive` directly, which re-exports this crate.
18//! For manual implementations:
19//!
20//! ```rust,ignore
21//! use entity_core::prelude::*;
22//!
23//! #[async_trait]
24//! impl UserRepository for MyPool {
25//!     type Error = MyError;
26//!     type Pool = PgPool;
27//!     // ...
28//! }
29//! ```
30
31#![warn(missing_docs)]
32#![warn(clippy::all)]
33
34pub mod policy;
35pub mod prelude;
36#[cfg(feature = "streams")]
37pub mod stream;
38pub mod transaction;
39
40/// Re-export async_trait for generated code.
41pub use async_trait::async_trait;
42
43/// Base repository trait.
44///
45/// All generated `{Entity}Repository` traits include these associated types
46/// and methods. This trait is not directly extended but serves as documentation
47/// for the common interface.
48///
49/// # Associated Types
50///
51/// - `Error` — Error type for repository operations
52/// - `Pool` — Underlying database pool type
53///
54/// # Example
55///
56/// Generated traits follow this pattern:
57///
58/// ```rust,ignore
59/// #[async_trait]
60/// pub trait UserRepository: Send + Sync {
61///     type Error: std::error::Error + Send + Sync;
62///     type Pool;
63///
64///     fn pool(&self) -> &Self::Pool;
65///     async fn create(&self, dto: CreateUserRequest) -> Result<User, Self::Error>;
66///     async fn find_by_id(&self, id: Uuid) -> Result<Option<User>, Self::Error>;
67///     // ...
68/// }
69/// ```
70pub trait Repository: Send + Sync {
71    /// Error type for repository operations.
72    ///
73    /// Must implement `std::error::Error + Send + Sync` for async
74    /// compatibility.
75    type Error: std::error::Error + Send + Sync;
76
77    /// Underlying database pool type.
78    ///
79    /// Enables access to the pool for transactions and custom queries.
80    type Pool;
81
82    /// Get reference to the underlying database pool.
83    ///
84    /// # Example
85    ///
86    /// ```rust,ignore
87    /// let pool = repo.pool();
88    /// let mut tx = pool.begin().await?;
89    /// // Custom operations...
90    /// tx.commit().await?;
91    /// ```
92    fn pool(&self) -> &Self::Pool;
93}
94
95/// Pagination parameters for list operations.
96///
97/// Used by `list` and `query` methods to control result pagination.
98///
99/// # Example
100///
101/// ```rust
102/// use entity_core::Pagination;
103///
104/// let page = Pagination::new(10, 0); // First 10 items
105/// let next = Pagination::new(10, 10); // Next 10 items
106/// ```
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108pub struct Pagination {
109    /// Maximum number of results to return.
110    pub limit: i64,
111
112    /// Number of results to skip.
113    pub offset: i64
114}
115
116impl Pagination {
117    /// Create new pagination parameters.
118    ///
119    /// # Arguments
120    ///
121    /// * `limit` — Maximum results to return
122    /// * `offset` — Number of results to skip
123    pub const fn new(limit: i64, offset: i64) -> Self {
124        Self {
125            limit,
126            offset
127        }
128    }
129
130    /// Create pagination for a specific page.
131    ///
132    /// # Arguments
133    ///
134    /// * `page` — Page number (0-indexed)
135    /// * `per_page` — Items per page
136    ///
137    /// # Example
138    ///
139    /// ```rust
140    /// use entity_core::Pagination;
141    ///
142    /// let page_0 = Pagination::page(0, 25); // offset=0, limit=25
143    /// let page_2 = Pagination::page(2, 25); // offset=50, limit=25
144    /// ```
145    pub const fn page(page: i64, per_page: i64) -> Self {
146        Self {
147            limit:  per_page,
148            offset: page * per_page
149        }
150    }
151}
152
153impl Default for Pagination {
154    fn default() -> Self {
155        Self {
156            limit:  100,
157            offset: 0
158        }
159    }
160}
161
162/// Sort direction for ordered queries.
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
164pub enum SortDirection {
165    /// Ascending order (A-Z, 0-9, oldest first).
166    #[default]
167    Asc,
168
169    /// Descending order (Z-A, 9-0, newest first).
170    Desc
171}
172
173impl SortDirection {
174    /// Convert to SQL keyword.
175    pub const fn as_sql(&self) -> &'static str {
176        match self {
177            Self::Asc => "ASC",
178            Self::Desc => "DESC"
179        }
180    }
181}
182
183/// Kind of lifecycle event.
184///
185/// Used by generated event enums to categorize events.
186#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
187pub enum EventKind {
188    /// Entity was created.
189    Created,
190
191    /// Entity was updated.
192    Updated,
193
194    /// Entity was soft-deleted.
195    SoftDeleted,
196
197    /// Entity was hard-deleted (permanently removed).
198    HardDeleted,
199
200    /// Entity was restored from soft-delete.
201    Restored
202}
203
204impl EventKind {
205    /// Check if this is a delete event (soft or hard).
206    pub const fn is_delete(&self) -> bool {
207        matches!(self, Self::SoftDeleted | Self::HardDeleted)
208    }
209
210    /// Check if this is a mutation event (create, update, delete).
211    pub const fn is_mutation(&self) -> bool {
212        !matches!(self, Self::Restored)
213    }
214}
215
216/// Base trait for entity lifecycle events.
217///
218/// Generated event enums implement this trait, enabling generic
219/// event handling and dispatching.
220///
221/// # Example
222///
223/// ```rust,ignore
224/// fn handle_event<E: EntityEvent>(event: &E) {
225///     println!("Event {:?} for entity {:?}", event.kind(), event.entity_id());
226/// }
227/// ```
228pub trait EntityEvent: Send + Sync + std::fmt::Debug {
229    /// Type of entity ID.
230    type Id;
231
232    /// Get the kind of event.
233    fn kind(&self) -> EventKind;
234
235    /// Get the entity ID associated with this event.
236    fn entity_id(&self) -> &Self::Id;
237}
238
239/// Kind of business command.
240///
241/// Used by generated command enums to categorize commands for auditing
242/// and routing purposes.
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
244pub enum CommandKind {
245    /// Creates a new entity (e.g., Register, Create).
246    Create,
247
248    /// Modifies an existing entity (e.g., UpdateEmail, ChangeStatus).
249    Update,
250
251    /// Removes an entity (e.g., Delete, Deactivate).
252    Delete,
253
254    /// Custom business operation that doesn't fit CRUD.
255    Custom
256}
257
258impl CommandKind {
259    /// Check if this command creates an entity.
260    pub const fn is_create(&self) -> bool {
261        matches!(self, Self::Create)
262    }
263
264    /// Check if this command modifies state.
265    pub const fn is_mutation(&self) -> bool {
266        !matches!(self, Self::Custom)
267    }
268}
269
270/// Base trait for entity commands.
271///
272/// Generated command enums implement this trait, enabling generic
273/// command handling, auditing, and dispatching.
274///
275/// # Example
276///
277/// ```rust,ignore
278/// fn audit_command<C: EntityCommand>(cmd: &C) {
279///     log::info!("Executing command: {} ({:?})", cmd.name(), cmd.kind());
280/// }
281/// ```
282pub trait EntityCommand: Send + Sync + std::fmt::Debug {
283    /// Get the kind of command for categorization.
284    fn kind(&self) -> CommandKind;
285
286    /// Get the command name as a string for logging/auditing.
287    fn name(&self) -> &'static str;
288}
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293
294    #[test]
295    fn pagination_new() {
296        let p = Pagination::new(50, 100);
297        assert_eq!(p.limit, 50);
298        assert_eq!(p.offset, 100);
299    }
300
301    #[test]
302    fn pagination_page() {
303        let p = Pagination::page(2, 25);
304        assert_eq!(p.limit, 25);
305        assert_eq!(p.offset, 50);
306    }
307
308    #[test]
309    fn pagination_default() {
310        let p = Pagination::default();
311        assert_eq!(p.limit, 100);
312        assert_eq!(p.offset, 0);
313    }
314
315    #[test]
316    fn sort_direction_sql() {
317        assert_eq!(SortDirection::Asc.as_sql(), "ASC");
318        assert_eq!(SortDirection::Desc.as_sql(), "DESC");
319    }
320
321    #[test]
322    fn sort_direction_default() {
323        assert_eq!(SortDirection::default(), SortDirection::Asc);
324    }
325
326    #[test]
327    fn event_kind_is_delete() {
328        assert!(!EventKind::Created.is_delete());
329        assert!(!EventKind::Updated.is_delete());
330        assert!(EventKind::SoftDeleted.is_delete());
331        assert!(EventKind::HardDeleted.is_delete());
332        assert!(!EventKind::Restored.is_delete());
333    }
334
335    #[test]
336    fn event_kind_is_mutation() {
337        assert!(EventKind::Created.is_mutation());
338        assert!(EventKind::Updated.is_mutation());
339        assert!(EventKind::SoftDeleted.is_mutation());
340        assert!(EventKind::HardDeleted.is_mutation());
341        assert!(!EventKind::Restored.is_mutation());
342    }
343
344    #[test]
345    fn command_kind_is_create() {
346        assert!(CommandKind::Create.is_create());
347        assert!(!CommandKind::Update.is_create());
348        assert!(!CommandKind::Delete.is_create());
349        assert!(!CommandKind::Custom.is_create());
350    }
351
352    #[test]
353    fn command_kind_is_mutation() {
354        assert!(CommandKind::Create.is_mutation());
355        assert!(CommandKind::Update.is_mutation());
356        assert!(CommandKind::Delete.is_mutation());
357        assert!(!CommandKind::Custom.is_mutation());
358    }
359}