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    /// Whether this event type has any `Forgettable<T>` fields.
67    ///
68    /// The `#[derive(EsEvent)]` macro sets this automatically via an inherent const
69    /// that shadows this default. Manual implementors can override it if needed.
70    #[doc(hidden)]
71    const HAS_FORGETTABLE_FIELDS: bool = false;
72}
73
74/// Required trait for converting new entities into their initial events before persistence.
75///
76/// All `NewEntity` types must implement this trait and its `into_events` method to emit the initial
77/// events that need to be persisted, later the `Entity` is re-constructed by replaying these events.
78///
79/// # Example
80///
81/// ```rust
82/// use es_entity::*;
83/// use serde::{Serialize, Deserialize};
84///
85/// entity_id!{ UserId }
86///
87/// #[derive(EsEvent, Serialize, Deserialize)]
88/// #[serde(tag = "type", rename_all = "snake_case")]
89/// #[es_event(id = "UserId")]
90/// pub enum UserEvent {
91///     Initialized { id: UserId, name: String },
92///     NameUpdated { name: String }
93/// }
94///
95/// // The main `Entity` type
96/// #[derive(EsEntity)]
97/// pub struct User {
98///     pub id: UserId,
99///     name: String,
100///     events: EntityEvents<UserEvent>
101/// }
102///
103/// // The `NewEntity` type used for initialization.
104/// pub struct NewUser {
105///     id: UserId,
106///     name: String
107/// }
108///
109/// // The `IntoEvents` implementation which emits an event stream.
110/// // These events help track `Entity` state mutations
111/// // Returns the `EntityEvents<UserEvent>`
112/// impl IntoEvents<UserEvent> for NewUser {
113///     fn into_events(self) -> EntityEvents<UserEvent> {
114///         EntityEvents::init(
115///             self.id,
116///             [UserEvent::Initialized {
117///                 id: self.id,
118///                 name: self.name,
119///             }],
120///         )
121///     }
122/// }
123///
124/// // The `TryFromEvents` implementation to hydrate entities by replaying events chronologically.
125/// impl TryFromEvents<UserEvent> for User {
126///     fn try_from_events(events: EntityEvents<UserEvent>) -> Result<Self, EntityHydrationError> {
127///         let mut name = String::new();
128///         for event in events.iter_all() {
129///              match event {
130///                 UserEvent::Initialized { name: n, .. } => name = n.clone(),
131///                 UserEvent::NameUpdated { name: n, .. } => name = n.clone(),
132///                 // ...similarly other events can be matched
133///             }
134///         }
135///         Ok(User { id: events.id().clone(), name, events })
136///     }
137/// }
138/// ```
139pub trait IntoEvents<E: EsEvent> {
140    /// Method to implement which emits event stream from a `NewEntity`
141    fn into_events(self) -> EntityEvents<E>;
142}
143
144/// Required trait for re-constructing entities from their events in chronological order.
145///
146/// All `Entity` types must implement this trait and its `try_from_events` method to hydrate
147/// entities post-persistence.
148///
149/// # Example
150///
151/// ```rust
152/// use es_entity::*;
153/// use serde::{Serialize, Deserialize};
154///
155/// entity_id!{ UserId }
156///
157/// #[derive(EsEvent, Serialize, Deserialize)]
158/// #[serde(tag = "type", rename_all = "snake_case")]
159/// #[es_event(id = "UserId")]
160/// pub enum UserEvent {
161///     Initialized { id: UserId, name: String },
162///     NameUpdated { name: String }
163/// }
164///
165/// // The main 'Entity' type
166/// #[derive(EsEntity)]
167/// pub struct User {
168///     pub id: UserId,
169///     name: String,
170///     events: EntityEvents<UserEvent>
171/// }
172///
173/// // The 'NewEntity' type used for initialization.
174/// pub struct NewUser {
175///     id: UserId,
176///     name: String
177/// }
178///
179/// // The IntoEvents implementation which emits an event stream.
180/// impl IntoEvents<UserEvent> for NewUser {
181///     fn into_events(self) -> EntityEvents<UserEvent> {
182///         EntityEvents::init(
183///             self.id,
184///             [UserEvent::Initialized {
185///                 id: self.id,
186///                 name: self.name,
187///             }],
188///         )
189///     }
190/// }
191///
192/// // The `TryFromEvents` implementation to hydrate entities by replaying events chronologically.
193/// // Returns the re-constructed `User` entity
194/// impl TryFromEvents<UserEvent> for User {
195///     fn try_from_events(events: EntityEvents<UserEvent>) -> Result<Self, EntityHydrationError> {
196///         let mut name = String::new();
197///         for event in events.iter_all() {
198///              match event {
199///                 UserEvent::Initialized { name: n, .. } => name = n.clone(),
200///                 UserEvent::NameUpdated { name: n, .. } => name = n.clone(),
201///                 // ...similarly other events can be matched
202///             }
203///         }
204///         Ok(User { id: events.id().clone(), name, events })
205///     }
206/// }
207/// ```
208pub trait TryFromEvents<E: EsEvent> {
209    /// Method to implement which hydrates `Entity` by replaying its events chronologically
210    fn try_from_events(events: EntityEvents<E>) -> Result<Self, EntityHydrationError>
211    where
212        Self: Sized;
213}
214
215/// Required trait for all entities to be compatible and recognised by es-entity.
216///
217/// All `Entity` types implement this trait to satisfy the basic requirements for
218/// event sourcing. The trait ensures the entity implements traits like `IntoEvents`
219/// and has the required components like `EntityEvent`, with helper methods to access the events sequence.
220/// Implemented by the [`EsEntity`][es_entity_macros::EsEntity] derive macro.
221///
222/// # Example
223///
224/// ```compile_fail
225/// use es_entity::*;
226/// use serde::{Serialize, Deserialize};
227///
228/// entity_id!{ UserId }
229///
230/// #[derive(EsEvent, Serialize, Deserialize)]
231/// #[serde(tag = "type", rename_all = "snake_case")]
232/// #[es_event(id = "UserId")]
233/// pub enum UserEvent {
234///     Initialized { id: UserId, name: String },
235/// }
236///
237/// // Compile-time error: Missing required trait implementations
238/// // - TryFromEvents<UserEvent> for User
239/// // - IntoEvents<UserEvent> for NewUser (associated type New)
240/// // - NewUser type definition
241/// #[derive(EsEntity)]
242/// pub struct User {
243///     pub id: UserId,
244///     pub name: String,
245///     events: EntityEvents<UserEvent>,
246/// }
247/// ```
248pub trait EsEntity: TryFromEvents<Self::Event> + Send {
249    type Event: EsEvent;
250    type New: IntoEvents<Self::Event>;
251
252    /// Returns an immutable reference to the entity's events
253    fn events(&self) -> &EntityEvents<Self::Event>;
254
255    /// Returns the last `n` persisted events
256    fn last_persisted(&self, n: usize) -> crate::events::LastPersisted<'_, Self::Event> {
257        self.events().last_persisted(n)
258    }
259
260    /// Returns mutable reference to the entity's events
261    fn events_mut(&mut self) -> &mut EntityEvents<Self::Event>;
262}
263
264/// Required trait for all repositories to be compatible with es-entity and generate functions.
265///
266/// All repositories implement this trait to satisfy the basic requirements for
267/// type-safe database operations with the associated entity. The trait ensures validation
268/// that required fields (like entity) are present with compile-time errors.
269/// Implemented by the [`EsRepo`][es_entity_macros::EsRepo] derive macro with `#[es_repo]` attributes.
270///
271/// # Example
272///
273/// ```ignore
274///
275/// // Would show error for missing entity field if not provided in the `es_repo` attribute
276/// #[derive(EsRepo, Debug)]
277/// #[es_repo(entity = "User", columns(name(ty = "String")))]
278/// pub struct Users {
279///     pool: PgPool,  // Required field for database operations
280/// }
281///
282/// impl Users {
283///     pub fn new(pool: PgPool) -> Self {
284///         Self { pool }
285///    }
286/// }
287/// ```
288pub trait EsRepo: Send {
289    type Entity: EsEntity;
290    type CreateError;
291    type ModifyError;
292    type FindError: From<sqlx::Error> + From<EntityHydrationError> + Send;
293    type QueryError: From<sqlx::Error> + From<EntityHydrationError> + Send;
294    type EsQueryFlavor;
295
296    /// Loads all nested entities for a given set of parent entities within an atomic operation.
297    fn load_all_nested_in_op<OP, E>(
298        op: &mut OP,
299        entities: &mut [Self::Entity],
300    ) -> impl Future<Output = Result<(), E>> + Send
301    where
302        OP: AtomicOperation,
303        E: From<sqlx::Error> + From<EntityHydrationError> + Send;
304
305    /// Like [`load_all_nested_in_op`](EsRepo::load_all_nested_in_op) but includes soft-deleted
306    /// children.
307    ///
308    /// Default implementation delegates to `load_all_nested_in_op`.
309    fn load_all_nested_in_op_include_deleted<OP, E>(
310        op: &mut OP,
311        entities: &mut [Self::Entity],
312    ) -> impl Future<Output = Result<(), E>> + Send
313    where
314        OP: AtomicOperation,
315        E: From<sqlx::Error> + From<EntityHydrationError> + Send,
316    {
317        Self::load_all_nested_in_op(op, entities)
318    }
319}
320
321pub trait RetryableInto<T>: Into<T> + Copy + std::fmt::Debug {}
322impl<T, O> RetryableInto<O> for T where T: Into<O> + Copy + std::fmt::Debug {}