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