es_entity/id_source.rs
1//! Where a repo's entities get their id from, and the one public route into
2//! an [`EntityEvents`] container.
3//!
4//! A repo resolves the id of every entity it creates according to its
5//! [`IdSource`]: [`FromRepo`] (the default, the repo generates it) or
6//! [`FromCaller`] (the caller puts it in the `New*` value). The
7//! [`IntoEvents`] implementation of a `New*` type receives an
8//! [`EntityEventsBuilder`] whose second parameter is that source, so
9//! [`EntityEventsBuilder::id`] exists only when the repo resolved one, and a
10//! [`FromRepo`] `New*` has no API through which a caller-chosen id can reach
11//! the insert.
12//!
13//! [`IntoEvents`]: crate::IntoEvents
14
15use crate::{events::EntityEvents, traits::EsEvent};
16
17mod sealed {
18 pub trait Sealed {}
19}
20
21/// Where the id of a newly created entity comes from. Sealed: [`FromRepo`]
22/// and [`FromCaller`] are the only sources.
23pub trait IdSource: sealed::Sealed {
24 /// What the [`EntityEventsBuilder`] carries: the resolved id for
25 /// [`FromRepo`], nothing for [`FromCaller`].
26 type Slot<Id>;
27}
28
29/// The repo resolved the id before `into_events` runs; the `New*` type has no
30/// id field and reads it with [`EntityEventsBuilder::id`].
31pub struct FromRepo;
32impl sealed::Sealed for FromRepo {}
33impl IdSource for FromRepo {
34 type Slot<Id> = Id;
35}
36
37/// The caller puts the id (a deterministic one) in the `New*` value, which
38/// hands it to [`EntityEventsBuilder::initial_events`]. A caller's id is a
39/// uniqueness claim, so the primary key stays among the repo's constraint
40/// violations.
41pub struct FromCaller;
42impl sealed::Sealed for FromCaller {}
43impl IdSource for FromCaller {
44 type Slot<Id> = ();
45}
46
47/// The id source applied, no events yet. Handed to
48/// [`IntoEvents::into_events`](crate::IntoEvents::into_events) by the
49/// generated repo code and consumed by `initial_events`, the only public way
50/// to build an [`EntityEvents`] container.
51pub struct EntityEventsBuilder<E: EsEvent, S: IdSource> {
52 id: S::Slot<E::EntityId>,
53}
54
55impl<E: EsEvent> EntityEventsBuilder<E, FromRepo> {
56 #[doc(hidden)]
57 pub fn __from_repo(id: E::EntityId) -> Self {
58 Self { id }
59 }
60
61 /// The id the repo resolved for this entity.
62 pub fn id(&self) -> E::EntityId {
63 self.id.clone()
64 }
65
66 /// The entity's initial events, under the resolved id.
67 pub fn initial_events(self, events: impl IntoIterator<Item = E>) -> EntityEvents<E> {
68 EntityEvents::init(self.id, events)
69 }
70}
71
72impl<E: EsEvent> EntityEventsBuilder<E, FromCaller> {
73 #[doc(hidden)]
74 pub fn __from_caller() -> Self {
75 Self { id: () }
76 }
77
78 /// The entity's initial events under the id the caller put in the `New*`
79 /// value.
80 pub fn initial_events(
81 self,
82 id: E::EntityId,
83 events: impl IntoIterator<Item = E>,
84 ) -> EntityEvents<E> {
85 EntityEvents::init(id, events)
86 }
87}
88
89/// What `create_or_find_by_id_in_op` did for a repo with deterministic ids.
90#[derive(Debug, Clone, PartialEq, Eq)]
91pub enum Ensured<E> {
92 /// This call inserted the entity.
93 Created(E),
94 /// The id already existed (a concurrent or earlier create won); this is
95 /// the stored entity.
96 Found(E),
97}
98
99impl<E> Ensured<E> {
100 /// The entity, however it came about.
101 pub fn into_inner(self) -> E {
102 match self {
103 Self::Created(entity) | Self::Found(entity) => entity,
104 }
105 }
106
107 pub fn is_created(&self) -> bool {
108 matches!(self, Self::Created(_))
109 }
110}