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