Skip to main content

es_entity/
traits.rs

1//! Traits to orchestrate and maintain the event-sourcing pattern.
2
3use serde::{Serialize, de::DeserializeOwned};
4
5use super::{db, error::EntityHydrationError, events::EntityEvents, operation::AtomicOperation};
6
7/// Required trait for all event enums to be compatible and recognised by es-entity.
8///
9/// All `EntityEvent` enums implement this trait to ensure it satisfies basic requirements for
10/// es-entity compatibility. The trait ensures trait implementations and compile-time validation that required fields (like id) are present.
11/// Implemented by the [`EsEvent`][es_entity_macros::EsEvent] derive macro with `#[es_event]` attribute.
12///
13/// # Example
14///
15/// ```compile_fail
16/// use es_entity::*;
17/// use serde::{Serialize, Deserialize};
18///
19/// entity_id!{ UserId }
20///
21/// // Compile-time error: missing `id` attribute in `es_event`
22/// #[derive(EsEvent, Serialize, Deserialize)]
23/// #[serde(tag = "type", rename_all = "snake_case")]
24/// // #[es_event(id = "UserId")] <- This line is required!
25/// pub enum UserEvent {
26///     Initialized { id: UserId, name: String },
27///     NameUpdated { name: String },
28///     Deactivated { reason: String }
29/// }
30/// ```
31///
32/// Correct usage:
33///
34/// ```rust
35/// use es_entity::*;
36/// use serde::{Serialize, Deserialize};
37///
38/// entity_id!{ UserId }
39///
40/// #[derive(EsEvent, Serialize, Deserialize)]
41/// #[serde(tag = "type", rename_all = "snake_case")]
42/// #[es_event(id = "UserId")]
43/// pub enum UserEvent {
44///     Initialized { id: UserId, name: String },
45///     NameUpdated { name: String },
46///     Deactivated { reason: String }
47/// }
48/// ```
49pub trait EsEvent: DeserializeOwned + Serialize + Send + Sync {
50    #[cfg(feature = "instrument")]
51    type EntityId: Clone
52        + PartialEq
53        + sqlx::Type<db::Db>
54        + Eq
55        + std::hash::Hash
56        + Send
57        + Sync
58        + std::fmt::Debug;
59
60    #[cfg(not(feature = "instrument"))]
61    type EntityId: Clone + PartialEq + sqlx::Type<db::Db> + Eq + std::hash::Hash + Send + Sync;
62
63    fn event_context() -> bool;
64    fn event_type(&self) -> &'static str;
65}
66
67/// Required trait for converting new entities into their initial events before persistence.
68///
69/// All `NewEntity` types must implement this trait and its `into_events` method to emit the initial
70/// events that need to be persisted, later the `Entity` is re-constructed by replaying these events.
71///
72/// # Example
73///
74/// ```rust
75/// use es_entity::*;
76/// use serde::{Serialize, Deserialize};
77///
78/// entity_id!{ UserId }
79///
80/// #[derive(EsEvent, Serialize, Deserialize)]
81/// #[serde(tag = "type", rename_all = "snake_case")]
82/// #[es_event(id = "UserId")]
83/// pub enum UserEvent {
84///     Initialized { id: UserId, name: String },
85///     NameUpdated { name: String }
86/// }
87///
88/// // The main `Entity` type
89/// #[derive(EsEntity)]
90/// pub struct User {
91///     pub id: UserId,
92///     name: String,
93///     events: EntityEvents<UserEvent>
94/// }
95///
96/// // The `NewEntity` type used for initialization.
97/// pub struct NewUser {
98///     id: UserId,
99///     name: String
100/// }
101///
102/// // The `IntoEvents` implementation which emits an event stream.
103/// // These events help track `Entity` state mutations
104/// // Returns the `EntityEvents<UserEvent>`
105/// impl IntoEvents<UserEvent> for NewUser {
106///     fn into_events(self) -> EntityEvents<UserEvent> {
107///         EntityEvents::init(
108///             self.id,
109///             [UserEvent::Initialized {
110///                 id: self.id,
111///                 name: self.name,
112///             }],
113///         )
114///     }
115/// }
116///
117/// // The `TryFromEvents` implementation to hydrate entities by replaying events chronologically.
118/// impl TryFromEvents<UserEvent> for User {
119///     fn try_from_events(events: EntityEvents<UserEvent>) -> Result<Self, EntityHydrationError> {
120///         let mut name = String::new();
121///         for event in events.iter_all() {
122///              match event {
123///                 UserEvent::Initialized { name: n, .. } => name = n.clone(),
124///                 UserEvent::NameUpdated { name: n, .. } => name = n.clone(),
125///                 // ...similarly other events can be matched
126///             }
127///         }
128///         Ok(User { id: events.id().clone(), name, events })
129///     }
130/// }
131/// ```
132pub trait IntoEvents<E: EsEvent> {
133    /// Method to implement which emits event stream from a `NewEntity`
134    fn into_events(self) -> EntityEvents<E>;
135}
136
137/// Required trait for re-constructing entities from their events in chronological order.
138///
139/// All `Entity` types must implement this trait and its `try_from_events` method to hydrate
140/// entities post-persistence.
141///
142/// # Example
143///
144/// ```rust
145/// use es_entity::*;
146/// use serde::{Serialize, Deserialize};
147///
148/// entity_id!{ UserId }
149///
150/// #[derive(EsEvent, Serialize, Deserialize)]
151/// #[serde(tag = "type", rename_all = "snake_case")]
152/// #[es_event(id = "UserId")]
153/// pub enum UserEvent {
154///     Initialized { id: UserId, name: String },
155///     NameUpdated { name: String }
156/// }
157///
158/// // The main 'Entity' type
159/// #[derive(EsEntity)]
160/// pub struct User {
161///     pub id: UserId,
162///     name: String,
163///     events: EntityEvents<UserEvent>
164/// }
165///
166/// // The 'NewEntity' type used for initialization.
167/// pub struct NewUser {
168///     id: UserId,
169///     name: String
170/// }
171///
172/// // The IntoEvents implementation which emits an event stream.
173/// impl IntoEvents<UserEvent> for NewUser {
174///     fn into_events(self) -> EntityEvents<UserEvent> {
175///         EntityEvents::init(
176///             self.id,
177///             [UserEvent::Initialized {
178///                 id: self.id,
179///                 name: self.name,
180///             }],
181///         )
182///     }
183/// }
184///
185/// // The `TryFromEvents` implementation to hydrate entities by replaying events chronologically.
186/// // Returns the re-constructed `User` entity
187/// impl TryFromEvents<UserEvent> for User {
188///     fn try_from_events(events: EntityEvents<UserEvent>) -> Result<Self, EntityHydrationError> {
189///         let mut name = String::new();
190///         for event in events.iter_all() {
191///              match event {
192///                 UserEvent::Initialized { name: n, .. } => name = n.clone(),
193///                 UserEvent::NameUpdated { name: n, .. } => name = n.clone(),
194///                 // ...similarly other events can be matched
195///             }
196///         }
197///         Ok(User { id: events.id().clone(), name, events })
198///     }
199/// }
200/// ```
201pub trait TryFromEvents<E: EsEvent> {
202    /// Method to implement which hydrates `Entity` by replaying its events chronologically
203    fn try_from_events(events: EntityEvents<E>) -> Result<Self, EntityHydrationError>
204    where
205        Self: Sized;
206}
207
208/// Required trait for all entities to be compatible and recognised by es-entity.
209///
210/// All `Entity` types implement this trait to satisfy the basic requirements for
211/// event sourcing. The trait ensures the entity implements traits like `IntoEvents`
212/// and has the required components like `EntityEvent`, with helper methods to access the events sequence.
213/// Implemented by the [`EsEntity`][es_entity_macros::EsEntity] derive macro.
214///
215/// # Example
216///
217/// ```compile_fail
218/// use es_entity::*;
219/// use serde::{Serialize, Deserialize};
220///
221/// entity_id!{ UserId }
222///
223/// #[derive(EsEvent, Serialize, Deserialize)]
224/// #[serde(tag = "type", rename_all = "snake_case")]
225/// #[es_event(id = "UserId")]
226/// pub enum UserEvent {
227///     Initialized { id: UserId, name: String },
228/// }
229///
230/// // Compile-time error: Missing required trait implementations
231/// // - TryFromEvents<UserEvent> for User
232/// // - IntoEvents<UserEvent> for NewUser (associated type New)
233/// // - NewUser type definition
234/// #[derive(EsEntity)]
235/// pub struct User {
236///     pub id: UserId,
237///     pub name: String,
238///     events: EntityEvents<UserEvent>,
239/// }
240/// ```
241pub trait EsEntity: TryFromEvents<Self::Event> + Send {
242    type Event: EsEvent;
243    type New: IntoEvents<Self::Event>;
244
245    /// Returns an immutable reference to the entity's events
246    fn events(&self) -> &EntityEvents<Self::Event>;
247
248    /// Returns the last `n` persisted events
249    fn last_persisted(&self, n: usize) -> crate::events::LastPersisted<'_, Self::Event> {
250        self.events().last_persisted(n)
251    }
252
253    /// Returns mutable reference to the entity's events
254    fn events_mut(&mut self) -> &mut EntityEvents<Self::Event>;
255}
256
257/// Required trait for all repositories to be compatible with es-entity and generate functions.
258///
259/// All repositories implement this trait to satisfy the basic requirements for
260/// type-safe database operations with the associated entity. The trait ensures validation
261/// that required fields (like entity) are present with compile-time errors.
262/// Implemented by the [`EsRepo`][es_entity_macros::EsRepo] derive macro with `#[es_repo]` attributes.
263///
264/// # Example
265///
266/// ```ignore
267///
268/// // Would show error for missing entity field if not provided in the `es_repo` attribute
269/// #[derive(EsRepo, Debug)]
270/// #[es_repo(entity = "User", columns(name(ty = "String")))]
271/// pub struct Users {
272///     pool: PgPool,  // Required field for database operations
273/// }
274///
275/// impl Users {
276///     pub fn new(pool: PgPool) -> Self {
277///         Self { pool }
278///    }
279/// }
280/// ```
281pub trait EsRepo: Send {
282    type Entity: EsEntity;
283    type CreateError;
284    type ModifyError;
285    type FindError: From<sqlx::Error> + From<EntityHydrationError> + Send;
286    type QueryError: From<sqlx::Error> + From<EntityHydrationError> + Send;
287    type EsQueryFlavor;
288
289    /// Loads all nested entities for a given set of parent entities within an atomic operation.
290    fn load_all_nested_in_op<OP, E>(
291        op: &mut OP,
292        entities: &mut [Self::Entity],
293    ) -> impl Future<Output = Result<(), E>> + Send
294    where
295        OP: AtomicOperation,
296        E: From<sqlx::Error> + From<EntityHydrationError> + Send;
297}
298
299pub trait RetryableInto<T>: Into<T> + Copy + std::fmt::Debug {}
300impl<T, O> RetryableInto<O> for T where T: Into<O> + Copy + std::fmt::Debug {}