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}