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}