Skip to main content

es_entity/
macros.rs

1/// Prevent duplicate event processing by checking for idempotent operations.
2///
3/// Guards against replaying the same mutation in event-sourced systems.
4/// Returns [`AlreadyApplied`][crate::Idempotent::AlreadyApplied] early if matching events are found, allowing the caller
5/// to skip redundant operations. Use `resets_on` to allow re-applying after an intervening event.
6///
7/// # Parameters
8///
9/// - `$events`: Event collection to search (usually chronologically reversed)
10/// - `already_applied:` One or more event patterns that indicate the operation was already applied.
11///   Multiple patterns are supported — each is checked independently.
12/// - `resets_on:` Optional event pattern that resets the guard, allowing re-execution.
13///   Use Rust's native or-pattern (`P1 | P2`) to match multiple reset events.
14///
15/// When iterating events in reverse, if a `resets_on` event is found before the
16/// `already_applied` event, the guard allows re-execution. This is useful for
17/// toggle-like operations (freeze/unfreeze) or when a state change should
18/// invalidate a previous idempotency check.
19///
20/// # Examples
21///
22/// ```rust
23/// use es_entity::{idempotency_guard, Idempotent};
24/// pub enum UserEvent{
25///     Initialized {id: u64, name: String},
26///     NameUpdated {name: String}
27/// }
28///
29/// pub struct User{
30///     events: Vec<UserEvent>
31/// }
32///
33/// impl User{
34///     pub fn update_name(&mut self, new_name: impl Into<String>) -> Idempotent<()>{
35///         let name = new_name.into();
36///         idempotency_guard!(
37///             self.events.iter().rev(),
38///             already_applied: UserEvent::NameUpdated { name: existing_name } if existing_name == &name
39///         );
40///         self.events.push(UserEvent::NameUpdated{name});
41///         Idempotent::Executed(())
42///     }
43///
44///     pub fn update_name_resettable(&mut self, new_name: impl Into<String>) -> Idempotent<()>{
45///         let name = new_name.into();
46///         idempotency_guard!(
47///             self.events.iter().rev(),
48///             already_applied: UserEvent::NameUpdated { name: existing_name } if existing_name == &name,
49///             resets_on: UserEvent::NameUpdated {..}
50///             // if any other NameUpdated happened more recently, allow re-applying
51///        );
52///        self.events.push(UserEvent::NameUpdated{name});
53///        Idempotent::Executed(())
54///     }
55/// }
56///
57/// let mut user1 = User{ events: vec![] };
58/// let mut user2 = User{ events: vec![] };
59/// assert!(user1.update_name("Alice").did_execute());
60/// // updating "Alice" again ignored because same event with same name exists
61/// assert!(user1.update_name("Alice").was_already_applied());
62///
63/// assert!(user2.update_name_resettable("Alice").did_execute());
64/// assert!(user2.update_name_resettable("Bob").did_execute());
65/// // updating "Alice" again works because Bob's NameUpdated resets the guard
66/// assert!(user2.update_name_resettable("Alice").did_execute());
67/// ```
68///
69/// ## Multiple `already_applied` patterns
70///
71/// ```rust
72/// use es_entity::{idempotency_guard, Idempotent};
73///
74/// pub enum ConfigEvent {
75///     Initialized { id: u64 },
76///     Updated { key: String, value: String },
77///     KeyRotated { key: String },
78/// }
79///
80/// pub struct Config {
81///     events: Vec<ConfigEvent>,
82/// }
83///
84/// impl Config {
85///     pub fn apply_change(&mut self, key: String, value: String) -> Idempotent<()> {
86///         idempotency_guard!(
87///             self.events.iter().rev(),
88///             already_applied: ConfigEvent::Updated { key: k, value: v } if k == &key && v == &value,
89///             already_applied: ConfigEvent::KeyRotated { key: k } if k == &key,
90///             resets_on: ConfigEvent::Initialized { .. }
91///         );
92///         self.events.push(ConfigEvent::Updated { key, value });
93///         Idempotent::Executed(())
94///     }
95/// }
96///
97/// let mut config = Config { events: vec![] };
98/// assert!(config.apply_change("k".into(), "v".into()).did_execute());
99/// assert!(config.apply_change("k".into(), "v".into()).was_already_applied());
100/// ```
101///
102/// ## Snapshotted streams: the `snapshot:` clause
103///
104/// Iterating `.replay()` (or `.replay_persisted()`) on an `EntityEvents<E, S>`
105/// with a real snapshot type yields [`Replay`][crate::Replay] items instead
106/// of plain events. `already_applied:` and `resets_on:` patterns still match
107/// `&E` values (wrapped in `Replay::Event` under the hood via
108/// [`IntoReplay`][crate::IntoReplay]), so existing call sites over
109/// `iter_all().rev()` compile unchanged. A stream that can yield
110/// `Replay::Snapshot` additionally needs a `snapshot:` clause — placed last,
111/// after `resets_on:` if present — saying what the snapshot implies about
112/// this operation. Omitting it on such a stream is a compile error: the
113/// macro expands to a call the snapshot's type does not implement, with a
114/// diagnostic pointing at the missing clause. Use `snapshot: _ if false` to
115/// say explicitly "the snapshot can never imply this was already applied".
116///
117/// `resets_on` semantics fold into the guard the same way: if the reset
118/// condition is itself something the snapshot could already reflect, encode
119/// the resolved state in the `snapshot:` guard (e.g.
120/// `snapshot: s if s.last_threshold_update == Some((lower, upper))`) rather
121/// than in a separate clause — the snapshot has no "reset" of its own, it is
122/// always the fold as of its sequence.
123///
124/// ```rust
125/// use es_entity::*;
126/// use serde::{Serialize, Deserialize};
127///
128/// entity_id! { MeterId }
129///
130/// #[derive(EsSnapshot, Serialize, Deserialize, Debug)]
131/// #[es_snapshot(version = 1)]
132/// pub struct MeterSnapshot { id: MeterId, last_value: Option<i64> }
133///
134/// #[derive(EsEvent, Serialize, Deserialize)]
135/// #[serde(tag = "type", rename_all = "snake_case")]
136/// #[es_event(id = "MeterId")]
137/// pub enum MeterEvent {
138///     Initialized { id: MeterId },
139///     ReadingRecorded { value: i64 },
140/// }
141///
142/// pub struct NewMeter;
143/// impl IntoEvents<MeterEvent> for NewMeter {
144///     type IdSource = FromRepo;
145///
146///     fn into_events(self, builder: EntityEventsBuilder<MeterEvent, FromRepo>) -> EntityEvents<MeterEvent> {
147///         let id = builder.id();
148///         builder.initial_events([MeterEvent::Initialized { id }])
149///     }
150/// }
151///
152/// #[derive(EsEntity)]
153/// pub struct Meter {
154///     pub id: MeterId,
155///     events: EntityEvents<MeterEvent, MeterSnapshot>,
156/// }
157///
158/// impl TryFromEvents<MeterEvent, MeterSnapshot> for Meter {
159///     fn try_from_events(
160///         events: EntityEvents<MeterEvent, MeterSnapshot>,
161///     ) -> Result<Self, EntityHydrationError> {
162///         let mut id = None;
163///         for r in events.replay() {
164///             match r {
165///                 Replay::Snapshot(s) => id = Some(s.id),
166///                 Replay::Event(MeterEvent::Initialized { id: i }) => id = Some(*i),
167///                 Replay::Event(_) => {}
168///             }
169///         }
170///         Ok(Meter { id: id.expect("Initialized"), events })
171///     }
172/// }
173///
174/// impl Meter {
175///     pub fn record(&mut self, value: i64) -> Idempotent<()> {
176///         idempotency_guard!(
177///             self.events.replay().rev(),
178///             already_applied: MeterEvent::ReadingRecorded { value: v } if *v == value,
179///             snapshot: s if s.last_value == Some(value),
180///         );
181///         self.events.push(MeterEvent::ReadingRecorded { value });
182///         Idempotent::Executed(())
183///     }
184/// }
185///
186/// use es_entity::testing::*;
187///
188/// let mut meter = Meter::hydrate_from_new(MeterId::new(), NewMeter).unwrap();
189/// assert!(meter.record(10).did_execute());
190/// assert!(meter.record(10).was_already_applied());
191/// ```
192///
193/// Omitting the `snapshot:` clause on the same stream does not compile:
194///
195/// ```compile_fail
196/// # use es_entity::*;
197/// # use serde::{Serialize, Deserialize};
198/// # entity_id! { MeterId }
199/// # #[derive(EsSnapshot, Serialize, Deserialize, Debug)]
200/// # #[es_snapshot(version = 1)]
201/// # pub struct MeterSnapshot { id: MeterId }
202/// # #[derive(EsEvent, Serialize, Deserialize)]
203/// # #[serde(tag = "type", rename_all = "snake_case")]
204/// # #[es_event(id = "MeterId")]
205/// # pub enum MeterEvent { Initialized { id: MeterId }, ReadingRecorded { value: i64 } }
206/// # pub struct NewMeter;
207/// # impl IntoEvents<MeterEvent> for NewMeter {
208/// #     type IdSource = FromRepo;
209/// #     fn into_events(self, builder: EntityEventsBuilder<MeterEvent, FromRepo>) -> EntityEvents<MeterEvent> {
210/// #         let id = builder.id();
211/// #         builder.initial_events([MeterEvent::Initialized { id }])
212/// #     }
213/// # }
214/// # #[derive(EsEntity)]
215/// # pub struct Meter { pub id: MeterId, events: EntityEvents<MeterEvent, MeterSnapshot> }
216/// # impl TryFromEvents<MeterEvent, MeterSnapshot> for Meter {
217/// #     fn try_from_events(events: EntityEvents<MeterEvent, MeterSnapshot>) -> Result<Self, EntityHydrationError> {
218/// #         unimplemented!()
219/// #     }
220/// # }
221/// impl Meter {
222///     pub fn record(&mut self, value: i64) -> Idempotent<()> {
223///         // error: missing the required `snapshot:` clause.
224///         idempotency_guard!(
225///             self.events.replay().rev(),
226///             already_applied: MeterEvent::ReadingRecorded { value: v } if *v == value,
227///         );
228///         self.events.push(MeterEvent::ReadingRecorded { value });
229///         Idempotent::Executed(())
230///     }
231/// }
232/// ```
233#[macro_export]
234macro_rules! idempotency_guard {
235    // already_applied+ , resets_on , snapshot
236    ($events:expr,
237     $(already_applied: $pattern:pat $(if $guard:expr)? ,)+
238     resets_on: $break_pattern:pat $(if $break_guard:expr)? ,
239     snapshot: $snap_pattern:pat $(if $snap_guard:expr)? $(,)?) => {
240        for __item in $events {
241            match $crate::IntoReplay::into_replay(__item) {
242                $(
243                    $crate::Replay::Event($pattern) $(if $guard)? => return $crate::FromAlreadyApplied::from_already_applied(),
244                )+
245                $crate::Replay::Snapshot($snap_pattern) $(if $snap_guard)? => return $crate::FromAlreadyApplied::from_already_applied(),
246                $crate::Replay::Event($break_pattern) $(if $break_guard)? => break,
247                _ => {}
248            }
249        }
250    };
251    // already_applied+ , resets_on (no snapshot clause -> compile error on a snapshotted stream)
252    ($events:expr,
253     $(already_applied: $pattern:pat $(if $guard:expr)? ,)+
254     resets_on: $break_pattern:pat $(if $break_guard:expr)? $(,)?) => {
255        for __item in $events {
256            match $crate::IntoReplay::into_replay(__item) {
257                $(
258                    $crate::Replay::Event($pattern) $(if $guard)? => return $crate::FromAlreadyApplied::from_already_applied(),
259                )+
260                $crate::Replay::Snapshot(__s) => return $crate::GuardWithoutSnapshotClause::reached(__s),
261                $crate::Replay::Event($break_pattern) $(if $break_guard)? => break,
262                _ => {}
263            }
264        }
265    };
266    // already_applied+ , snapshot
267    ($events:expr,
268     $(already_applied: $pattern:pat $(if $guard:expr)? ,)+
269     snapshot: $snap_pattern:pat $(if $snap_guard:expr)? $(,)?) => {
270        for __item in $events {
271            match $crate::IntoReplay::into_replay(__item) {
272                $(
273                    $crate::Replay::Event($pattern) $(if $guard)? => return $crate::FromAlreadyApplied::from_already_applied(),
274                )+
275                $crate::Replay::Snapshot($snap_pattern) $(if $snap_guard)? => return $crate::FromAlreadyApplied::from_already_applied(),
276                _ => {}
277            }
278        }
279    };
280    // already_applied+ only (no snapshot clause -> compile error on a snapshotted stream)
281    ($events:expr,
282     $(already_applied: $pattern:pat $(if $guard:expr)?),+ $(,)?) => {
283        for __item in $events {
284            match $crate::IntoReplay::into_replay(__item) {
285                $(
286                    $crate::Replay::Event($pattern) $(if $guard)? => return $crate::FromAlreadyApplied::from_already_applied(),
287                )+
288                $crate::Replay::Snapshot(__s) => return $crate::GuardWithoutSnapshotClause::reached(__s),
289                _ => {}
290            }
291        }
292    };
293}
294
295/// Execute an event-sourced query with automatic entity hydration.
296///
297/// Executes user-defined queries and returns entities by internally
298/// joining with events table to hydrate entities, essentially giving the
299/// illusion of working with just the index table.
300///
301/// **Important**: This macro only works inside functions (`fn`) that are defined
302/// within structs that have `#[derive(EsRepo)]` applied. The macro relies on
303/// the repository context to properly hydrate entities.
304///
305/// # Returns
306///
307/// Returns an [`EsQuery`](crate::query::EsQuery) struct that provides methods
308/// like [`fetch_optional()`](crate::query::EsQuery::fetch_optional) and
309/// [`fetch_n()`](crate::query::EsQuery::fetch_n) for executing the
310/// query and retrieving hydrated entities.
311///
312/// # Parameters
313///
314/// - `tbl_prefix`: Table prefix to ignore when deriving entity names from table names (optional)
315/// - `entity`: Override the entity type (optional, useful when table name doesn't match entity name)
316/// - SQL query string
317/// - Additional arguments for the SQL query (optional)
318///
319/// # Examples
320/// ```ignore
321/// // Basic usage
322/// es_query!("SELECT id FROM users WHERE id = $1", id)
323///
324/// // With table prefix
325/// es_query!(
326///     tbl_prefix = "app",
327///     "SELECT id FROM app_users WHERE active = true"
328/// )
329///
330/// // With custom entity type
331/// es_query!(
332///     entity = User,
333///     "SELECT id FROM custom_users_table WHERE id = $1",
334///     id as UserId
335/// )
336/// ```
337#[macro_export]
338macro_rules! es_query {
339    // With entity override + forgettable + snapshot
340    (
341        entity = $entity:ident,
342        forgettable_tbl = $forgettable_tbl:literal,
343        snapshot_tbl = $snapshot_tbl:literal,
344        $query:expr,
345        $($args:tt)*
346    ) => ({
347        $crate::expand_es_query!(
348            entity = $entity,
349            forgettable_tbl = $forgettable_tbl,
350            snapshot_tbl = $snapshot_tbl,
351            sql = $query,
352            args = [$($args)*]
353        )
354    });
355    // With entity override + forgettable + snapshot - no args
356    (
357        entity = $entity:ident,
358        forgettable_tbl = $forgettable_tbl:literal,
359        snapshot_tbl = $snapshot_tbl:literal,
360        $query:expr
361    ) => ({
362        $crate::expand_es_query!(
363            entity = $entity,
364            forgettable_tbl = $forgettable_tbl,
365            snapshot_tbl = $snapshot_tbl,
366            sql = $query
367        )
368    });
369    // With entity override + snapshot (no forgettable)
370    (
371        entity = $entity:ident,
372        snapshot_tbl = $snapshot_tbl:literal,
373        $query:expr,
374        $($args:tt)*
375    ) => ({
376        $crate::expand_es_query!(
377            entity = $entity,
378            snapshot_tbl = $snapshot_tbl,
379            sql = $query,
380            args = [$($args)*]
381        )
382    });
383    // With entity override + snapshot (no forgettable) - no args
384    (
385        entity = $entity:ident,
386        snapshot_tbl = $snapshot_tbl:literal,
387        $query:expr
388    ) => ({
389        $crate::expand_es_query!(
390            entity = $entity,
391            snapshot_tbl = $snapshot_tbl,
392            sql = $query
393        )
394    });
395
396    // With entity override + forgettable
397    (
398        entity = $entity:ident,
399        forgettable_tbl = $forgettable_tbl:literal,
400        $query:expr,
401        $($args:tt)*
402    ) => ({
403        $crate::expand_es_query!(
404            entity = $entity,
405            forgettable_tbl = $forgettable_tbl,
406            sql = $query,
407            args = [$($args)*]
408        )
409    });
410    // With entity override + forgettable - no args
411    (
412        entity = $entity:ident,
413        forgettable_tbl = $forgettable_tbl:literal,
414        $query:expr
415    ) => ({
416        $crate::expand_es_query!(
417            entity = $entity,
418            forgettable_tbl = $forgettable_tbl,
419            sql = $query
420        )
421    });
422
423    // With entity override
424    (
425        entity = $entity:ident,
426        $query:expr,
427        $($args:tt)*
428    ) => ({
429        $crate::expand_es_query!(
430            entity = $entity,
431            sql = $query,
432            args = [$($args)*]
433        )
434    });
435    // With entity override - no args
436    (
437        entity = $entity:ident,
438        $query:expr
439    ) => ({
440        $crate::expand_es_query!(
441            entity = $entity,
442            sql = $query
443        )
444    });
445
446    // With tbl_prefix + forgettable + snapshot
447    (
448        tbl_prefix = $tbl_prefix:literal,
449        forgettable_tbl = $forgettable_tbl:literal,
450        snapshot_tbl = $snapshot_tbl:literal,
451        $query:expr,
452        $($args:tt)*
453    ) => ({
454        $crate::expand_es_query!(
455            tbl_prefix = $tbl_prefix,
456            forgettable_tbl = $forgettable_tbl,
457            snapshot_tbl = $snapshot_tbl,
458            sql = $query,
459            args = [$($args)*]
460        )
461    });
462    // With tbl_prefix + forgettable + snapshot - no args
463    (
464        tbl_prefix = $tbl_prefix:literal,
465        forgettable_tbl = $forgettable_tbl:literal,
466        snapshot_tbl = $snapshot_tbl:literal,
467        $query:expr
468    ) => ({
469        $crate::expand_es_query!(
470            tbl_prefix = $tbl_prefix,
471            forgettable_tbl = $forgettable_tbl,
472            snapshot_tbl = $snapshot_tbl,
473            sql = $query
474        )
475    });
476    // With tbl_prefix + snapshot (no forgettable)
477    (
478        tbl_prefix = $tbl_prefix:literal,
479        snapshot_tbl = $snapshot_tbl:literal,
480        $query:expr,
481        $($args:tt)*
482    ) => ({
483        $crate::expand_es_query!(
484            tbl_prefix = $tbl_prefix,
485            snapshot_tbl = $snapshot_tbl,
486            sql = $query,
487            args = [$($args)*]
488        )
489    });
490    // With tbl_prefix + snapshot (no forgettable) - no args
491    (
492        tbl_prefix = $tbl_prefix:literal,
493        snapshot_tbl = $snapshot_tbl:literal,
494        $query:expr
495    ) => ({
496        $crate::expand_es_query!(
497            tbl_prefix = $tbl_prefix,
498            snapshot_tbl = $snapshot_tbl,
499            sql = $query
500        )
501    });
502
503    // With tbl_prefix + forgettable
504    (
505        tbl_prefix = $tbl_prefix:literal,
506        forgettable_tbl = $forgettable_tbl:literal,
507        $query:expr,
508        $($args:tt)*
509    ) => ({
510        $crate::expand_es_query!(
511            tbl_prefix = $tbl_prefix,
512            forgettable_tbl = $forgettable_tbl,
513            sql = $query,
514            args = [$($args)*]
515        )
516    });
517    // With tbl_prefix + forgettable - no args
518    (
519        tbl_prefix = $tbl_prefix:literal,
520        forgettable_tbl = $forgettable_tbl:literal,
521        $query:expr
522    ) => ({
523        $crate::expand_es_query!(
524            tbl_prefix = $tbl_prefix,
525            forgettable_tbl = $forgettable_tbl,
526            sql = $query
527        )
528    });
529
530    // With tbl_prefix
531    (
532        tbl_prefix = $tbl_prefix:literal,
533        $query:expr,
534        $($args:tt)*
535    ) => ({
536        $crate::expand_es_query!(
537            tbl_prefix = $tbl_prefix,
538            sql = $query,
539            args = [$($args)*]
540        )
541    });
542    // With tbl_prefix - no args
543    (
544        tbl_prefix = $tbl_prefix:literal,
545        $query:expr
546    ) => ({
547        $crate::expand_es_query!(
548            tbl_prefix = $tbl_prefix,
549            sql = $query
550        )
551    });
552
553    // Basic form
554    (
555        $query:expr,
556        $($args:tt)*
557    ) => ({
558        $crate::expand_es_query!(
559            sql = $query,
560            args = [$($args)*]
561        )
562    });
563    // Basic form - no args
564    (
565        $query:expr
566    ) => ({
567        $crate::expand_es_query!(
568            sql = $query
569        )
570    });
571}
572
573// Helper macro for common entity_id implementations (internal use only)
574#[doc(hidden)]
575#[macro_export]
576macro_rules! __entity_id_common_impls {
577    ($name:ident) => {
578        impl $name {
579            #[allow(clippy::new_without_default)]
580            pub fn new() -> Self {
581                $crate::prelude::uuid::Uuid::now_v7().into()
582            }
583        }
584
585        impl From<$crate::prelude::uuid::Uuid> for $name {
586            fn from(uuid: $crate::prelude::uuid::Uuid) -> Self {
587                Self(uuid)
588            }
589        }
590
591        impl From<$name> for $crate::prelude::uuid::Uuid {
592            fn from(id: $name) -> Self {
593                id.0
594            }
595        }
596
597        impl From<&$name> for $crate::prelude::uuid::Uuid {
598            fn from(id: &$name) -> Self {
599                id.0
600            }
601        }
602
603        impl std::fmt::Display for $name {
604            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
605                write!(f, "{}", self.0)
606            }
607        }
608
609        impl std::str::FromStr for $name {
610            type Err = $crate::prelude::uuid::Error;
611
612            fn from_str(s: &str) -> Result<Self, Self::Err> {
613                Ok(Self($crate::prelude::uuid::Uuid::parse_str(s)?))
614            }
615        }
616    };
617}
618
619// Helper macro for GraphQL-specific entity_id implementations (internal use only)
620// When `graphql` feature is enabled, entity IDs become their own GraphQL scalars
621// (e.g. `CustomerId` instead of the generic `UUID`), providing type safety at the API layer.
622#[doc(hidden)]
623#[macro_export]
624macro_rules! __entity_id_graphql_impls {
625    ($name:ident) => {
626        impl From<$crate::graphql::UUID> for $name {
627            fn from(id: $crate::graphql::UUID) -> Self {
628                $name($crate::prelude::uuid::Uuid::from(&id))
629            }
630        }
631
632        impl From<&$crate::graphql::UUID> for $name {
633            fn from(id: &$crate::graphql::UUID) -> Self {
634                $name($crate::prelude::uuid::Uuid::from(id))
635            }
636        }
637
638        $crate::graphql::async_graphql::scalar!($name);
639    };
640}
641
642// Helper macro for additional conversions (internal use only)
643#[doc(hidden)]
644#[macro_export]
645macro_rules! __entity_id_conversions {
646    ($($from:ty => $to:ty),* $(,)?) => {
647        $(
648            impl From<$from> for $to {
649                fn from(id: $from) -> Self {
650                    <$to>::from($crate::prelude::uuid::Uuid::from(id))
651                }
652            }
653            impl From<$to> for $from {
654                fn from(id: $to) -> Self {
655                    <$from>::from($crate::prelude::uuid::Uuid::from(id))
656                }
657            }
658        )*
659    };
660}
661
662#[doc(hidden)]
663#[cfg(all(feature = "graphql", feature = "json-schema"))]
664#[macro_export]
665macro_rules! entity_id {
666    // Match identifiers without conversions
667    ($($name:ident),+ $(,)?) => {
668        $crate::entity_id! { $($name),+ ; }
669    };
670    ($($name:ident),+ $(,)? ; $($from:ty => $to:ty),* $(,)?) => {
671        $(
672            #[derive(
673                $crate::prelude::sqlx::Type,
674                Debug,
675                Clone,
676                Copy,
677                PartialEq,
678                Eq,
679                PartialOrd,
680                Ord,
681                Hash,
682                $crate::prelude::serde::Deserialize,
683                $crate::prelude::serde::Serialize,
684                $crate::prelude::schemars::JsonSchema,
685            )]
686            #[schemars(crate = "es_entity::prelude::schemars")]
687            #[serde(crate = "es_entity::prelude::serde")]
688            #[serde(transparent)]
689            #[sqlx(transparent)]
690            pub struct $name($crate::prelude::uuid::Uuid);
691            $crate::__entity_id_common_impls!($name);
692            $crate::__entity_id_graphql_impls!($name);
693
694        )+
695        $crate::__entity_id_conversions!($($from => $to),*);
696    };
697}
698
699#[doc(hidden)]
700#[cfg(all(feature = "graphql", not(feature = "json-schema")))]
701#[macro_export]
702macro_rules! entity_id {
703    // Match identifiers without conversions
704    ($($name:ident),+ $(,)?) => {
705        $crate::entity_id! { $($name),+ ; }
706    };
707    ($($name:ident),+ $(,)? ; $($from:ty => $to:ty),* $(,)?) => {
708        $(
709            #[derive(
710                $crate::prelude::sqlx::Type,
711                Debug,
712                Clone,
713                Copy,
714                PartialEq,
715                Eq,
716                PartialOrd,
717                Ord,
718                Hash,
719                $crate::prelude::serde::Deserialize,
720                $crate::prelude::serde::Serialize,
721            )]
722            #[serde(crate = "es_entity::prelude::serde")]
723            #[serde(transparent)]
724            #[sqlx(transparent)]
725            pub struct $name($crate::prelude::uuid::Uuid);
726            $crate::__entity_id_common_impls!($name);
727            $crate::__entity_id_graphql_impls!($name);
728
729        )+
730        $crate::__entity_id_conversions!($($from => $to),*);
731    };
732}
733
734#[doc(hidden)]
735#[cfg(all(feature = "json-schema", not(feature = "graphql")))]
736#[macro_export]
737macro_rules! entity_id {
738    // Match identifiers without conversions
739    ($($name:ident),+ $(,)?) => {
740        $crate::entity_id! { $($name),+ ; }
741    };
742    ($($name:ident),+ $(,)? ; $($from:ty => $to:ty),* $(,)?) => {
743        $(
744            #[derive(
745                $crate::prelude::sqlx::Type,
746                Debug,
747                Clone,
748                Copy,
749                PartialEq,
750                Eq,
751                PartialOrd,
752                Ord,
753                Hash,
754                $crate::prelude::serde::Deserialize,
755                $crate::prelude::serde::Serialize,
756                $crate::prelude::schemars::JsonSchema,
757            )]
758            #[schemars(crate = "es_entity::prelude::schemars")]
759            #[serde(crate = "es_entity::prelude::serde")]
760            #[serde(transparent)]
761            #[sqlx(transparent)]
762            pub struct $name($crate::prelude::uuid::Uuid);
763            $crate::__entity_id_common_impls!($name);
764
765        )+
766        $crate::__entity_id_conversions!($($from => $to),*);
767    };
768}
769
770/// Create UUID-wrappers for database operations.
771///
772/// This macro generates type-safe UUID-wrapper structs with trait support for
773/// serialization, database operations, GraphQL integration, and JSON schema generation.
774///
775/// # Features
776///
777/// The macro automatically includes different trait implementations based on enabled features:
778/// - `graphql`: Adds GraphQL UUID conversion traits and registers each entity ID as its own
779///   GraphQL scalar type (e.g. `CustomerId` instead of the generic `UUID`), providing type
780///   safety at the API layer
781/// - `json-schema`: Adds JSON schema generation support
782///
783/// # Generated Traits
784///
785/// All entity IDs automatically implement:
786/// - `Debug`, `Clone`, `Copy`, `PartialEq`, `Eq`, `PartialOrd`, `Ord`, `Hash`
787/// - `serde::Serialize`, `serde::Deserialize` (with transparent serialization)
788/// - `sqlx::Type` (with transparent database type)
789/// - `Display` and `FromStr` for string conversion
790/// - `From<Uuid>` and `From<EntityId>` for UUID conversion
791///
792/// # Parameters
793///
794/// - `$name`: One or more entity ID type names to create
795/// - `$from => $to`: Optional conversion pairs between different entity ID types
796///
797/// # Examples
798///
799/// ```rust
800/// use es_entity::entity_id;
801///
802/// entity_id! { UserId, OrderId }
803///
804/// // Creates:
805/// // pub struct UserId(Uuid);
806/// // pub struct OrderId(Uuid);
807/// ```
808///
809/// ```rust
810/// use es_entity::entity_id;
811///
812/// entity_id! {
813///     UserId,
814///     AdminUserId;
815///     UserId => AdminUserId
816/// }
817///
818/// // Creates UserId and AdminUserId with `impl From` conversion between them
819/// ```
820#[cfg(all(not(feature = "json-schema"), not(feature = "graphql")))]
821#[macro_export]
822macro_rules! entity_id {
823    // Match identifiers without conversions
824    ($($name:ident),+ $(,)?) => {
825        $crate::entity_id! { $($name),+ ; }
826    };
827    ($($name:ident),+ $(,)? ; $($from:ty => $to:ty),* $(,)?) => {
828        $(
829            #[derive(
830                $crate::prelude::sqlx::Type,
831                Debug,
832                Clone,
833                Copy,
834                PartialEq,
835                Eq,
836                PartialOrd,
837                Ord,
838                Hash,
839                $crate::prelude::serde::Deserialize,
840                $crate::prelude::serde::Serialize,
841            )]
842            #[serde(crate = "es_entity::prelude::serde")]
843            #[serde(transparent)]
844            #[sqlx(transparent)]
845            pub struct $name($crate::prelude::uuid::Uuid);
846            $crate::__entity_id_common_impls!($name);
847
848        )+
849        $crate::__entity_id_conversions!($($from => $to),*);
850    };
851}
852
853/// Implements [`AtomicOperation`](crate::AtomicOperation) for a type by
854/// delegating every method to an operation it holds.
855///
856/// A type that wraps or dispatches to another operation — a newtype over
857/// `&mut DbOp` that seals off `commit()`, a restricted view handed to a
858/// callback, an enum choosing between several ops — needs all of
859/// `AtomicOperation` forwarded. Written by hand that is eight near-identical
860/// bodies per type, and a method left out silently inherits a trait default:
861/// `maybe_now` starts reporting `None`, `supports_hooks` `false`, or
862/// [`savepoint_parts`](crate::AtomicOperation::savepoint_parts) reports no hook
863/// buffer while the wrapped op has one. The behaviour changes and nothing fails
864/// to compile.
865///
866/// This macro generates the whole impl, so those cannot drift apart, and it adds
867/// **no** public accessor — the wrapped operation stays as private as it was.
868/// That matters for types that withhold `&mut` access on purpose: exposing it
869/// would let a caller swap the operation out or commit it directly.
870///
871/// # Newtype
872///
873/// ```rust,ignore
874/// struct FlushOp<'a>(&'a mut es_entity::DbOp<'static>);
875///
876/// es_entity::delegate_atomic_operation!(FlushOp<'_>, { s => s.0 });
877/// ```
878///
879/// # Enum
880///
881/// Each arm names a pattern and the operation to delegate to. Arms may hold
882/// different types — `DbOp`, `&mut DbOp`, `&mut SavepointOp` — because the
883/// generated code calls the method inside each arm rather than unifying the
884/// arms into one value.
885///
886/// ```rust,ignore
887/// es_entity::delegate_atomic_operation!(UseCaseOp<'_, '_>, {
888///     Self::Owned(op) => op,
889///     Self::Db(op) => op,
890///     Self::Savepoint(op) => op,
891/// });
892/// ```
893///
894/// # Generic types
895///
896/// Pass the impl generics in brackets first:
897///
898/// ```rust,ignore
899/// es_entity::delegate_atomic_operation!([<'a, T: es_entity::AtomicOperation>] MyOp<'a, T>, {
900///     s => s.inner
901/// });
902/// ```
903///
904/// # When not to use it
905///
906/// Only for pure delegation. A wrapper that changes behaviour — reporting its
907/// own cached time, refusing hooks — must hand-write the impl, since this macro
908/// forwards every method.
909#[macro_export]
910macro_rules! delegate_atomic_operation {
911    // Bracketed generics first: otherwise the bare arm below tries to parse a
912    // leading `[` as the start of a slice type.
913    ([$($generics:tt)*] $ty:ty, { $($pat:pat => $target:expr),+ $(,)? }) => {
914        impl $($generics)* $crate::AtomicOperation for $ty {
915            fn maybe_now(
916                &self,
917            ) -> Option<$crate::prelude::chrono::DateTime<$crate::prelude::chrono::Utc>> {
918                match self { $($pat => $target.maybe_now()),+ }
919            }
920
921            fn clock(&self) -> &$crate::clock::ClockHandle {
922                match self { $($pat => $target.clock()),+ }
923            }
924
925            fn connection(&mut self) -> &mut $crate::db::Connection {
926                match self { $($pat => $target.connection()),+ }
927            }
928
929            fn as_executor(
930                &mut self,
931            ) -> $crate::OneTimeExecutor<'_, &mut $crate::db::Connection> {
932                match self { $($pat => $target.as_executor()),+ }
933            }
934
935            fn add_commit_hook_dyn(
936                &mut self,
937                type_id: std::any::TypeId,
938                hook: Box<dyn $crate::hooks::DynHook>,
939            ) -> Result<(), Box<dyn $crate::hooks::DynHook>> {
940                match self { $($pat => $target.add_commit_hook_dyn(type_id, hook)),+ }
941            }
942
943            fn commit_hook_dyn(
944                &self,
945                type_id: std::any::TypeId,
946            ) -> Option<&dyn $crate::hooks::DynHook> {
947                match self { $($pat => $target.commit_hook_dyn(type_id)),+ }
948            }
949
950            fn supports_hooks(&self) -> bool {
951                match self { $($pat => $target.supports_hooks()),+ }
952            }
953
954            fn savepoint_parts(
955                &mut self,
956            ) -> (&mut $crate::db::Connection, $crate::HookSlot<'_>) {
957                match self { $($pat => $target.savepoint_parts()),+ }
958            }
959        }
960    };
961
962    // Bare form: no impl generics.
963    ($ty:ty, { $($pat:pat => $target:expr),+ $(,)? }) => {
964        $crate::delegate_atomic_operation!([] $ty, { $($pat => $target),+ });
965    };
966}