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