Skip to main content

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    #[must_use]
124    pub const fn new(limit: i64, offset: i64) -> Self {
125        Self {
126            limit,
127            offset
128        }
129    }
130
131    /// Create pagination for a specific page.
132    ///
133    /// # Arguments
134    ///
135    /// * `page` — Page number (0-indexed)
136    /// * `per_page` — Items per page
137    ///
138    /// # Example
139    ///
140    /// ```rust
141    /// use entity_core::Pagination;
142    ///
143    /// let page_0 = Pagination::page(0, 25); // offset=0, limit=25
144    /// let page_2 = Pagination::page(2, 25); // offset=50, limit=25
145    /// ```
146    #[must_use]
147    pub const fn page(page: i64, per_page: i64) -> Self {
148        Self {
149            limit:  per_page,
150            offset: page * per_page
151        }
152    }
153}
154
155impl Default for Pagination {
156    fn default() -> Self {
157        Self {
158            limit:  100,
159            offset: 0
160        }
161    }
162}
163
164/// Sort direction for ordered queries.
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
166pub enum SortDirection {
167    /// Ascending order (A-Z, 0-9, oldest first).
168    #[default]
169    Asc,
170
171    /// Descending order (Z-A, 9-0, newest first).
172    Desc
173}
174
175impl SortDirection {
176    /// Convert to SQL keyword.
177    #[must_use]
178    pub const fn as_sql(&self) -> &'static str {
179        match self {
180            Self::Asc => "ASC",
181            Self::Desc => "DESC"
182        }
183    }
184}
185
186/// Kind of lifecycle event.
187///
188/// Used by generated event enums to categorize events.
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
190pub enum EventKind {
191    /// Entity was created.
192    Created,
193
194    /// Entity was updated.
195    Updated,
196
197    /// Entity was soft-deleted.
198    SoftDeleted,
199
200    /// Entity was hard-deleted (permanently removed).
201    HardDeleted,
202
203    /// Entity was restored from soft-delete.
204    Restored
205}
206
207impl EventKind {
208    /// Check if this is a delete event (soft or hard).
209    #[must_use]
210    pub const fn is_delete(&self) -> bool {
211        matches!(self, Self::SoftDeleted | Self::HardDeleted)
212    }
213
214    /// Check if this is a mutation event (create, update, delete).
215    #[must_use]
216    pub const fn is_mutation(&self) -> bool {
217        !matches!(self, Self::Restored)
218    }
219}
220
221/// Base trait for entity lifecycle events.
222///
223/// Generated event enums implement this trait, enabling generic
224/// event handling and dispatching.
225///
226/// # Example
227///
228/// ```rust,ignore
229/// fn handle_event<E: EntityEvent>(event: &E) {
230///     println!("Event {:?} for entity {:?}", event.kind(), event.entity_id());
231/// }
232/// ```
233pub trait EntityEvent: Send + Sync + std::fmt::Debug {
234    /// Type of entity ID.
235    type Id;
236
237    /// Get the kind of event.
238    fn kind(&self) -> EventKind;
239
240    /// Get the entity ID associated with this event.
241    fn entity_id(&self) -> &Self::Id;
242}
243
244/// Kind of business command.
245///
246/// Used by generated command enums to categorize commands for auditing
247/// and routing purposes.
248#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
249pub enum CommandKind {
250    /// Creates a new entity (e.g., Register, Create).
251    Create,
252
253    /// Modifies an existing entity (e.g., `UpdateEmail`, `ChangeStatus`).
254    Update,
255
256    /// Removes an entity (e.g., Delete, Deactivate).
257    Delete,
258
259    /// Custom business operation that doesn't fit CRUD.
260    Custom
261}
262
263impl CommandKind {
264    /// Check if this command creates an entity.
265    #[must_use]
266    pub const fn is_create(&self) -> bool {
267        matches!(self, Self::Create)
268    }
269
270    /// Check if this command modifies state.
271    #[must_use]
272    pub const fn is_mutation(&self) -> bool {
273        !matches!(self, Self::Custom)
274    }
275}
276
277/// Base trait for entity commands.
278///
279/// Generated command enums implement this trait, enabling generic
280/// command handling, auditing, and dispatching.
281///
282/// # Example
283///
284/// ```rust,ignore
285/// fn audit_command<C: EntityCommand>(cmd: &C) {
286///     log::info!("Executing command: {} ({:?})", cmd.name(), cmd.kind());
287/// }
288/// ```
289pub trait EntityCommand: Send + Sync + std::fmt::Debug {
290    /// Get the kind of command for categorization.
291    fn kind(&self) -> CommandKind;
292
293    /// Get the command name as a string for logging/auditing.
294    fn name(&self) -> &'static str;
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    #[test]
302    fn pagination_new() {
303        let p = Pagination::new(50, 100);
304        assert_eq!(p.limit, 50);
305        assert_eq!(p.offset, 100);
306    }
307
308    #[test]
309    fn pagination_page() {
310        let p = Pagination::page(2, 25);
311        assert_eq!(p.limit, 25);
312        assert_eq!(p.offset, 50);
313    }
314
315    #[test]
316    fn pagination_default() {
317        let p = Pagination::default();
318        assert_eq!(p.limit, 100);
319        assert_eq!(p.offset, 0);
320    }
321
322    #[test]
323    fn sort_direction_sql() {
324        assert_eq!(SortDirection::Asc.as_sql(), "ASC");
325        assert_eq!(SortDirection::Desc.as_sql(), "DESC");
326    }
327
328    #[test]
329    fn sort_direction_default() {
330        assert_eq!(SortDirection::default(), SortDirection::Asc);
331    }
332
333    #[test]
334    fn event_kind_is_delete() {
335        assert!(!EventKind::Created.is_delete());
336        assert!(!EventKind::Updated.is_delete());
337        assert!(EventKind::SoftDeleted.is_delete());
338        assert!(EventKind::HardDeleted.is_delete());
339        assert!(!EventKind::Restored.is_delete());
340    }
341
342    #[test]
343    fn event_kind_is_mutation() {
344        assert!(EventKind::Created.is_mutation());
345        assert!(EventKind::Updated.is_mutation());
346        assert!(EventKind::SoftDeleted.is_mutation());
347        assert!(EventKind::HardDeleted.is_mutation());
348        assert!(!EventKind::Restored.is_mutation());
349    }
350
351    #[test]
352    fn command_kind_is_create() {
353        assert!(CommandKind::Create.is_create());
354        assert!(!CommandKind::Update.is_create());
355        assert!(!CommandKind::Delete.is_create());
356        assert!(!CommandKind::Custom.is_create());
357    }
358
359    #[test]
360    fn command_kind_is_mutation() {
361        assert!(CommandKind::Create.is_mutation());
362        assert!(CommandKind::Update.is_mutation());
363        assert!(CommandKind::Delete.is_mutation());
364        assert!(!CommandKind::Custom.is_mutation());
365    }
366}