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