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 std::collections::HashMap;
6
7use super::{db, error::EntityHydrationError, events::EntityEvents, tree_query::TreeSpec};
8
9/// Required trait for all event enums to be compatible and recognised by es-entity.
10///
11/// All `EntityEvent` enums implement this trait to ensure it satisfies basic requirements for
12/// es-entity compatibility. The trait ensures trait implementations and compile-time validation that required fields (like id) are present.
13/// Implemented by the [`EsEvent`][es_entity_macros::EsEvent] derive macro with `#[es_event]` attribute.
14///
15/// # Example
16///
17/// ```compile_fail
18/// use es_entity::*;
19/// use serde::{Serialize, Deserialize};
20///
21/// entity_id!{ UserId }
22///
23/// // Compile-time error: missing `id` attribute in `es_event`
24/// #[derive(EsEvent, Serialize, Deserialize)]
25/// #[serde(tag = "type", rename_all = "snake_case")]
26/// // #[es_event(id = "UserId")] <- This line is required!
27/// pub enum UserEvent {
28///     Initialized { id: UserId, name: String },
29///     NameUpdated { name: String },
30///     Deactivated { reason: String }
31/// }
32/// ```
33///
34/// Correct usage:
35///
36/// ```rust
37/// use es_entity::*;
38/// use serde::{Serialize, Deserialize};
39///
40/// entity_id!{ UserId }
41///
42/// #[derive(EsEvent, Serialize, Deserialize)]
43/// #[serde(tag = "type", rename_all = "snake_case")]
44/// #[es_event(id = "UserId")]
45/// pub enum UserEvent {
46///     Initialized { id: UserId, name: String },
47///     NameUpdated { name: String },
48///     Deactivated { reason: String }
49/// }
50/// ```
51pub trait EsEvent: DeserializeOwned + Serialize + Send + Sync {
52    #[cfg(feature = "instrument")]
53    type EntityId: Clone
54        + PartialEq
55        + sqlx::Type<db::Db>
56        + Eq
57        + std::hash::Hash
58        + Send
59        + Sync
60        + std::fmt::Debug;
61
62    #[cfg(not(feature = "instrument"))]
63    type EntityId: Clone + PartialEq + sqlx::Type<db::Db> + Eq + std::hash::Hash + Send + Sync;
64
65    fn event_context() -> bool;
66    fn event_type(&self) -> &'static str;
67
68    /// Whether this event type has any `Forgettable<T>` fields.
69    ///
70    /// The `#[derive(EsEvent)]` macro sets this automatically via an inherent const
71    /// that shadows this default. Manual implementors can override it if needed.
72    #[doc(hidden)]
73    const HAS_FORGETTABLE_FIELDS: bool = false;
74}
75
76/// Required trait for converting new entities into their initial events before persistence.
77///
78/// All `NewEntity` types must implement this trait and its `into_events` method to emit the initial
79/// events that need to be persisted, later the `Entity` is re-constructed by replaying these events.
80///
81/// # Example
82///
83/// ```rust
84/// use es_entity::*;
85/// use serde::{Serialize, Deserialize};
86///
87/// entity_id!{ UserId }
88///
89/// #[derive(EsEvent, Serialize, Deserialize)]
90/// #[serde(tag = "type", rename_all = "snake_case")]
91/// #[es_event(id = "UserId")]
92/// pub enum UserEvent {
93///     Initialized { id: UserId, name: String },
94///     NameUpdated { name: String }
95/// }
96///
97/// // The main `Entity` type
98/// #[derive(EsEntity)]
99/// pub struct User {
100///     pub id: UserId,
101///     name: String,
102///     events: EntityEvents<UserEvent>
103/// }
104///
105/// // The `NewEntity` type used for initialization.
106/// pub struct NewUser {
107///     id: UserId,
108///     name: String
109/// }
110///
111/// // The `IntoEvents` implementation which emits an event stream.
112/// // These events help track `Entity` state mutations
113/// // Returns the `EntityEvents<UserEvent>`
114/// impl IntoEvents<UserEvent> for NewUser {
115///     fn into_events(self) -> EntityEvents<UserEvent> {
116///         EntityEvents::init(
117///             self.id,
118///             [UserEvent::Initialized {
119///                 id: self.id,
120///                 name: self.name,
121///             }],
122///         )
123///     }
124/// }
125///
126/// // The `TryFromEvents` implementation to hydrate entities by replaying events chronologically.
127/// impl TryFromEvents<UserEvent> for User {
128///     fn try_from_events(events: EntityEvents<UserEvent>) -> Result<Self, EntityHydrationError> {
129///         let mut name = String::new();
130///         for event in events.iter_all() {
131///              match event {
132///                 UserEvent::Initialized { name: n, .. } => name = n.clone(),
133///                 UserEvent::NameUpdated { name: n, .. } => name = n.clone(),
134///                 // ...similarly other events can be matched
135///             }
136///         }
137///         Ok(User { id: events.id().clone(), name, events })
138///     }
139/// }
140/// ```
141pub trait IntoEvents<E: EsEvent> {
142    /// Method to implement which emits event stream from a `NewEntity`
143    fn into_events(self) -> EntityEvents<E>;
144}
145
146/// Required trait for re-constructing entities from their events in chronological order.
147///
148/// All `Entity` types must implement this trait and its `try_from_events` method to hydrate
149/// entities post-persistence.
150///
151/// # Example
152///
153/// ```rust
154/// use es_entity::*;
155/// use serde::{Serialize, Deserialize};
156///
157/// entity_id!{ UserId }
158///
159/// #[derive(EsEvent, Serialize, Deserialize)]
160/// #[serde(tag = "type", rename_all = "snake_case")]
161/// #[es_event(id = "UserId")]
162/// pub enum UserEvent {
163///     Initialized { id: UserId, name: String },
164///     NameUpdated { name: String }
165/// }
166///
167/// // The main 'Entity' type
168/// #[derive(EsEntity)]
169/// pub struct User {
170///     pub id: UserId,
171///     name: String,
172///     events: EntityEvents<UserEvent>
173/// }
174///
175/// // The 'NewEntity' type used for initialization.
176/// pub struct NewUser {
177///     id: UserId,
178///     name: String
179/// }
180///
181/// // The IntoEvents implementation which emits an event stream.
182/// impl IntoEvents<UserEvent> for NewUser {
183///     fn into_events(self) -> EntityEvents<UserEvent> {
184///         EntityEvents::init(
185///             self.id,
186///             [UserEvent::Initialized {
187///                 id: self.id,
188///                 name: self.name,
189///             }],
190///         )
191///     }
192/// }
193///
194/// // The `TryFromEvents` implementation to hydrate entities by replaying events chronologically.
195/// // Returns the re-constructed `User` entity
196/// impl TryFromEvents<UserEvent> for User {
197///     fn try_from_events(events: EntityEvents<UserEvent>) -> Result<Self, EntityHydrationError> {
198///         let mut name = String::new();
199///         for event in events.iter_all() {
200///              match event {
201///                 UserEvent::Initialized { name: n, .. } => name = n.clone(),
202///                 UserEvent::NameUpdated { name: n, .. } => name = n.clone(),
203///                 // ...similarly other events can be matched
204///             }
205///         }
206///         Ok(User { id: events.id().clone(), name, events })
207///     }
208/// }
209/// ```
210pub trait TryFromEvents<E: EsEvent> {
211    /// Method to implement which hydrates `Entity` by replaying its events chronologically
212    fn try_from_events(events: EntityEvents<E>) -> Result<Self, EntityHydrationError>
213    where
214        Self: Sized;
215}
216
217/// Required trait for all entities to be compatible and recognised by es-entity.
218///
219/// All `Entity` types implement this trait to satisfy the basic requirements for
220/// event sourcing. The trait ensures the entity implements traits like `IntoEvents`
221/// and has the required components like `EntityEvent`, with helper methods to access the events sequence.
222/// Implemented by the [`EsEntity`][es_entity_macros::EsEntity] derive macro.
223///
224/// # Example
225///
226/// ```compile_fail
227/// use es_entity::*;
228/// use serde::{Serialize, Deserialize};
229///
230/// entity_id!{ UserId }
231///
232/// #[derive(EsEvent, Serialize, Deserialize)]
233/// #[serde(tag = "type", rename_all = "snake_case")]
234/// #[es_event(id = "UserId")]
235/// pub enum UserEvent {
236///     Initialized { id: UserId, name: String },
237/// }
238///
239/// // Compile-time error: Missing required trait implementations
240/// // - TryFromEvents<UserEvent> for User
241/// // - IntoEvents<UserEvent> for NewUser (associated type New)
242/// // - NewUser type definition
243/// #[derive(EsEntity)]
244/// pub struct User {
245///     pub id: UserId,
246///     pub name: String,
247///     events: EntityEvents<UserEvent>,
248/// }
249/// ```
250pub trait EsEntity: TryFromEvents<Self::Event> + Send {
251    type Event: EsEvent;
252    type New: IntoEvents<Self::Event>;
253
254    /// Returns an immutable reference to the entity's events
255    fn events(&self) -> &EntityEvents<Self::Event>;
256
257    /// Returns the last `n` persisted events
258    fn last_persisted(&self, n: usize) -> crate::events::LastPersisted<'_, Self::Event> {
259        self.events().last_persisted(n)
260    }
261
262    /// Returns mutable reference to the entity's events
263    fn events_mut(&mut self) -> &mut EntityEvents<Self::Event>;
264}
265
266/// Required trait for all repositories to be compatible with es-entity and generate functions.
267///
268/// All repositories implement this trait to satisfy the basic requirements for
269/// type-safe database operations with the associated entity. The trait ensures validation
270/// that required fields (like entity) are present with compile-time errors.
271/// Implemented by the [`EsRepo`][es_entity_macros::EsRepo] derive macro with `#[es_repo]` attributes.
272///
273/// # Example
274///
275/// ```ignore
276///
277/// // Would show error for missing entity field if not provided in the `es_repo` attribute
278/// #[derive(EsRepo, Debug)]
279/// #[es_repo(entity = "User", columns(name(ty = "String")))]
280/// pub struct Users {
281///     pool: PgPool,  // Required field for database operations
282/// }
283///
284/// impl Users {
285///     pub fn new(pool: PgPool) -> Self {
286///         Self { pool }
287///    }
288/// }
289/// ```
290///
291/// # `in_op_only`: making the operation mandatory
292///
293/// `#[es_repo(in_op_only)]` generates **only** the `_in_op` variants of every
294/// repo fn. The standalone fns are exactly the ones that open (or borrow) the
295/// pool on the caller's behalf, so removing them makes passing an operation —
296/// an [`AtomicOperation`][crate::AtomicOperation] for writes, an
297/// [`IntoOneTimeExecutor`][crate::IntoOneTimeExecutor] for reads — the only way
298/// to reach the database.
299///
300/// With no standalone fn left to open one, the pool field becomes optional. A
301/// repo that holds no pool cannot begin its own operation, which is the point:
302/// the discipline is enforced by construction rather than by convention.
303///
304/// Calling a non-`_in_op` fn on such a repo does not compile — the method does
305/// not exist (note the two tests below compile a real repo, so they need the
306/// test database, like the book's examples):
307///
308/// ```compile_fail,E0599
309/// use es_entity::*;
310/// use serde::{Deserialize, Serialize};
311/// # fn main() {}
312/// # es_entity::entity_id! { UserId }
313/// # #[derive(EsEvent, Debug, Serialize, Deserialize)]
314/// # #[serde(tag = "type", rename_all = "snake_case")]
315/// # #[es_event(id = "UserId")]
316/// # pub enum UserEvent {
317/// #     Initialized { id: UserId, name: String },
318/// # }
319/// # pub struct NewUser { id: UserId, name: String }
320/// # impl IntoEvents<UserEvent> for NewUser {
321/// #     fn into_events(self) -> EntityEvents<UserEvent> { unimplemented!() }
322/// # }
323/// # #[derive(EsEntity)]
324/// # pub struct User {
325/// #     pub id: UserId,
326/// #     pub name: String,
327/// #     events: EntityEvents<UserEvent>,
328/// # }
329/// # impl TryFromEvents<UserEvent> for User {
330/// #     fn try_from_events(events: EntityEvents<UserEvent>) -> Result<Self, EntityHydrationError> {
331/// #         unimplemented!()
332/// #     }
333/// # }
334/// // This repo deliberately KEEPS its pool, so that `create`, were it ever
335/// // generated again, would compile: that makes the E0599 below prove the fn
336/// // is absent, rather than merely that its body could not build an operation.
337/// #[derive(EsRepo)]
338/// #[es_repo(entity = "User", in_op_only, columns(name(ty = "String")))]
339/// pub struct Users {
340///     pool: es_entity::db::Pool,
341/// }
342///
343/// async fn reaches_the_db_without_an_op(repo: &Users, new_user: NewUser) {
344///     // error[E0599]: no method named `create` found — `in_op_only` leaves
345///     // only `create_in_op`, which demands an operation from the caller.
346///     repo.create(new_user).await.unwrap();
347/// }
348/// ```
349///
350/// The `_in_op` twin of the very same call compiles:
351///
352/// ```
353/// use es_entity::*;
354/// use serde::{Deserialize, Serialize};
355/// # fn main() {}
356/// # es_entity::entity_id! { UserId }
357/// # #[derive(EsEvent, Debug, Serialize, Deserialize)]
358/// # #[serde(tag = "type", rename_all = "snake_case")]
359/// # #[es_event(id = "UserId")]
360/// # pub enum UserEvent {
361/// #     Initialized { id: UserId, name: String },
362/// # }
363/// # pub struct NewUser { id: UserId, name: String }
364/// # impl IntoEvents<UserEvent> for NewUser {
365/// #     fn into_events(self) -> EntityEvents<UserEvent> { unimplemented!() }
366/// # }
367/// # #[derive(EsEntity)]
368/// # pub struct User {
369/// #     pub id: UserId,
370/// #     pub name: String,
371/// #     events: EntityEvents<UserEvent>,
372/// # }
373/// # impl TryFromEvents<UserEvent> for User {
374/// #     fn try_from_events(events: EntityEvents<UserEvent>) -> Result<Self, EntityHydrationError> {
375/// #         unimplemented!()
376/// #     }
377/// # }
378/// #[derive(EsRepo)]
379/// #[es_repo(entity = "User", in_op_only, columns(name(ty = "String")))]
380/// pub struct Users {}
381///
382/// async fn takes_the_op_from_its_caller(
383///     repo: &Users,
384///     op: &mut impl AtomicOperation,
385///     new_user: NewUser,
386/// ) {
387///     repo.create_in_op(op, new_user).await.unwrap();
388/// }
389/// ```
390pub trait EsRepo: Send {
391    type Entity: EsEntity;
392    type CreateError;
393    type ModifyError;
394    type FindError: From<sqlx::Error> + From<EntityHydrationError> + Send;
395    type QueryError: From<sqlx::Error> + From<EntityHydrationError> + Send;
396    type EsQueryFlavor;
397
398    fn nested_tree_spec() -> TreeSpec;
399
400    fn hydrate_nested_from_rows<E>(
401        rows_by_tag: &mut HashMap<i32, Vec<db::Row>>,
402        tag_cursor: &mut i32,
403        entities: &mut [Self::Entity],
404    ) -> Result<(), E>
405    where
406        E: From<sqlx::Error> + From<EntityHydrationError>;
407}
408
409pub trait RetryableInto<T>: Into<T> + Copy + std::fmt::Debug {}
410impl<T, O> RetryableInto<O> for T where T: Into<O> + Copy + std::fmt::Debug {}