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 {}