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