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