Skip to main content

behavior/
user_event.rs

1//! The user-message lane and its composition contracts.
2
3use crate::actor::Address;
4
5/// The current layer of a structurally composed event algebra owns an input.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub struct Here;
8
9/// An inner event algebra owns an input at `Path`.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct Inside<Path>(core::marker::PhantomData<fn() -> Path>);
12
13/// Address-free destination for one interpreter-originated input.
14///
15/// Unlike an actor [`Recipient`](crate::Recipient), this capability targets one
16/// exact member of the current actor's composed ingress algebra. Service
17/// requests retain it so the eventual fact does not have to search the final
18/// behavior type for a matching payload. `Path` is compile-time evidence and
19/// occupies no runtime storage.
20pub struct Ingress<Input, Path> {
21    marker: core::marker::PhantomData<fn(Input, Path)>,
22}
23
24impl<Input, Path> Copy for Ingress<Input, Path> {}
25
26impl<Input, Path> Clone for Ingress<Input, Path> {
27    fn clone(&self) -> Self {
28        *self
29    }
30}
31
32impl<Input, Path> Ingress<Input, Path> {
33    /// Select a statically proven ingress member.
34    #[must_use]
35    pub const fn new() -> Self {
36        Self {
37            marker: core::marker::PhantomData,
38        }
39    }
40
41    /// Construct the exact concrete event without runtime route discovery.
42    #[must_use]
43    pub fn event<Event>(self, input: Input) -> Event
44    where
45        Event: InjectEvent<Input, Path>,
46    {
47        Event::inject_at(input)
48    }
49
50    /// Lift this destination through one outer structural event layer.
51    #[must_use]
52    pub const fn inside(self) -> Ingress<Input, Inside<Path>> {
53        Ingress {
54            marker: core::marker::PhantomData,
55        }
56    }
57}
58
59impl<Input, Path> Default for Ingress<Input, Path> {
60    fn default() -> Self {
61        Self::new()
62    }
63}
64
65impl<Input, Path> core::fmt::Debug for Ingress<Input, Path> {
66    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
67        formatter.write_str("Ingress")
68    }
69}
70
71impl<Input, Path> PartialEq for Ingress<Input, Path> {
72    fn eq(&self, _: &Self) -> bool {
73        true
74    }
75}
76
77impl<Input, Path> Eq for Ingress<Input, Path> {}
78
79/// Owner-selected event ingress for one statically identified source.
80///
81/// `Source` distinguishes otherwise identical inputs without an absolute
82/// wrapper path. A child report uses its semantic [`ChildRole`](crate::ChildRole)
83/// as the source; a current-actor policy input uses [`Here`]. Outer
84/// [`EventLayer`] composition lifts the selected
85/// ingress automatically, so adding a [`BehaviorLayer`](crate::BehaviorLayer)
86/// never requires a caller to recount structural event depth.
87///
88/// This is a derived Bombay composition contract, not an actor-model
89/// primitive. It constructs a typed event only and performs no delivery,
90/// lookup, or interpreter effect.
91pub trait EventIngress<Source, Input>: Sized {
92    /// Construct the event selected for `Source` and `Input`.
93    fn ingress(input: Input) -> Self;
94}
95
96/// Construction of one private input sent by an established parent to its
97/// concrete direct child.
98///
99/// This is the event-side contract of [`ChildInput`](crate::ChildInput). It is
100/// deliberately distinct from [`EventIngress`], which selects a parent event
101/// for an incoming child report or a same-actor owner input. Keeping the two
102/// directions distinct lets a report-owning behavior transformation preserve
103/// every inner child-input capability without knowing its source or payload.
104///
105/// `Source` identifies the behavior law that owns `Input`; it is neither an
106/// actor address nor runtime lookup key. Constructing the event performs no
107/// delivery or other actor effect.
108pub trait ChildInputIngress<Source, Input>: Sized {
109    /// Construct the concrete child event selected by `Source` and `Input`.
110    fn child_input(input: Input) -> Self;
111
112    /// Recover the original input from this port's selected event lane.
113    ///
114    /// Recovering an event constructed by `child_input` must return its exact
115    /// original input. A foreign event is returned complete and unchanged.
116    ///
117    /// # Errors
118    /// Returns the complete original event when it belongs to another lane.
119    fn recover_child_input(event: Self) -> Result<Input, Self>;
120}
121
122/// One statically owned input lane composed in front of an inner event algebra.
123///
124/// `EventLayer` is a concrete coproduct, not an erased envelope. `Owned` is the
125/// lane introduced by the current behavior template and `Inner` preserves the
126/// complete event algebra of the wrapped behavior.
127#[derive(Debug, Clone, PartialEq, Eq)]
128pub enum EventLayer<Owned, Inner> {
129    Owned(Owned),
130    Inner(Inner),
131}
132
133impl<Owned, Inner, Source, Input> ChildInputIngress<Source, Input> for EventLayer<Owned, Inner>
134where
135    Inner: ChildInputIngress<Source, Input>,
136{
137    fn child_input(input: Input) -> Self {
138        Self::Inner(Inner::child_input(input))
139    }
140
141    fn recover_child_input(event: Self) -> Result<Input, Self> {
142        match event {
143            Self::Inner(inner) => Inner::recover_child_input(inner).map_err(Self::Inner),
144            owned @ Self::Owned(_) => Err(owned),
145        }
146    }
147}
148
149impl<Owned, Inner, Source, Input> EventIngress<Source, Input> for EventLayer<Owned, Inner>
150where
151    Inner: EventIngress<Source, Input>,
152{
153    fn ingress(input: Input) -> Self {
154        Self::Inner(Inner::ingress(input))
155    }
156}
157
158/// A complete event algebra formed by adding owned inputs around an inner
159/// behavior event algebra.
160///
161/// `from_inner` is the structure-preserving injection used by effect
162/// composition. A single owned lane uses [`EventLayer`]; a domain with several
163/// genuinely coexisting owned inputs may use a named exhaustive sum.
164pub trait ComposedEvent: UserEvent {
165    type Inner: UserEvent<Addr = Self::Addr, Message = Self::Message>;
166
167    fn from_inner(event: Self::Inner) -> Self;
168}
169
170impl<Owned, Inner> ComposedEvent for EventLayer<Owned, Inner>
171where
172    Inner: UserEvent,
173{
174    type Inner = Inner;
175
176    fn from_inner(event: Inner) -> Self {
177        Self::Inner(event)
178    }
179}
180
181/// Path-indexed injection into a structural event coproduct.
182///
183/// The path is compile-time routing evidence. It prevents the overlapping
184/// implementations that arise when an outer layer owns the same input type as
185/// an inner layer and makes that ownership choice explicit in the type system.
186/// Unsupported input has no construction capability:
187///
188/// ```compile_fail
189/// let _ = <behavior::User<behavior::MailAddr, ()> as behavior::InjectEvent<u8, behavior::Here>>::inject_at(7);
190/// ```
191///
192/// Repeated payload types require an explicit ownership path rather than an
193/// outermost-first runtime guess:
194///
195/// ```compile_fail
196/// type Duplicate = behavior::EventLayer<u8, behavior::EventLayer<u8, behavior::User<behavior::MailAddr, ()>>>;
197/// let _ = <Duplicate as behavior::InjectEvent<u8, _>>::inject_at(7);
198/// ```
199pub trait InjectEvent<Input, Path>: Sized {
200    fn inject_at(input: Input) -> Self;
201}
202
203/// Exact recovery of one statically selected input from a returned event.
204///
205/// A failed runtime admission returns the complete root event. This ownership
206/// port recovers the original input without cloning, downcasting, or searching
207/// another lane. An event from a different lane is returned unchanged.
208/// Discarding the returned event cannot preserve an affine input:
209///
210/// ```compile_fail,E0382
211/// type Event = behavior::EventLayer<
212///     String,
213///     behavior::User<behavior::MailAddr, ()>,
214/// >;
215/// let input = String::from("worker");
216/// let event = <Event as behavior::InjectEvent<String, behavior::Here>>::inject_at(input);
217/// drop(event);
218/// drop(input);
219/// ```
220pub trait RecoverEvent<Input, Path>: Sized {
221    /// Recover `Input` when this event belongs to `Path`.
222    ///
223    /// # Errors
224    /// Returns the unchanged event when another lane owns it.
225    fn recover(event: Self) -> Result<Input, Self>;
226}
227
228impl<Owned, Inner> InjectEvent<Owned, Here> for EventLayer<Owned, Inner> {
229    fn inject_at(input: Owned) -> Self {
230        Self::Owned(input)
231    }
232}
233
234impl<Owned, Inner> RecoverEvent<Owned, Here> for EventLayer<Owned, Inner> {
235    fn recover(event: Self) -> Result<Owned, Self> {
236        match event {
237            Self::Owned(input) => Ok(input),
238            other @ Self::Inner(_) => Err(other),
239        }
240    }
241}
242
243impl<Owned, Inner, Input, Path> InjectEvent<Input, Inside<Path>> for EventLayer<Owned, Inner>
244where
245    Inner: InjectEvent<Input, Path>,
246{
247    fn inject_at(input: Input) -> Self {
248        Self::Inner(Inner::inject_at(input))
249    }
250}
251
252impl<Owned, Inner, Input, Path> RecoverEvent<Input, Inside<Path>> for EventLayer<Owned, Inner>
253where
254    Inner: RecoverEvent<Input, Path>,
255{
256    fn recover(event: Self) -> Result<Input, Self> {
257        match event {
258            Self::Inner(inner) => Inner::recover(inner).map_err(Self::Inner),
259            other @ Self::Owned(_) => Err(other),
260        }
261    }
262}
263
264/// The user-message event at the Agha floor.
265#[derive(Debug, Clone, PartialEq, Eq)]
266pub struct User<A, M> {
267    pub from: A,
268    pub message: M,
269}
270
271impl<A, M> User<A, M> {
272    #[must_use]
273    pub const fn new(from: A, message: M) -> Self {
274        Self { from, message }
275    }
276}
277
278impl<A, M> From<(A, M)> for User<A, M> {
279    fn from((from, message): (A, M)) -> Self {
280        Self::new(from, message)
281    }
282}
283
284/// Construction/extraction of the user lane through a composed event type.
285pub trait UserEvent: Sized {
286    type Addr: Address;
287    type Message;
288
289    fn user(from: Self::Addr, message: Self::Message) -> Self;
290
291    /// # Errors
292    /// Returns the unchanged event when it belongs to another composed lane.
293    fn into_user(self) -> Result<User<Self::Addr, Self::Message>, Self>;
294}
295
296impl<A: Address, M> UserEvent for User<A, M> {
297    type Addr = A;
298    type Message = M;
299
300    fn user(from: A, message: M) -> Self {
301        Self::new(from, message)
302    }
303    fn into_user(self) -> Result<Self, Self> {
304        Ok(self)
305    }
306}
307
308impl<Owned, Inner> UserEvent for EventLayer<Owned, Inner>
309where
310    Inner: UserEvent,
311{
312    type Addr = Inner::Addr;
313    type Message = Inner::Message;
314
315    fn user(from: Self::Addr, message: Self::Message) -> Self {
316        Self::Inner(Inner::user(from, message))
317    }
318
319    fn into_user(self) -> Result<User<Self::Addr, Self::Message>, Self> {
320        match self {
321            Self::Inner(inner) => inner.into_user().map_err(Self::Inner),
322            owned @ Self::Owned(_) => Err(owned),
323        }
324    }
325}
326
327#[cfg(test)]
328mod structural_tests {
329    use super::*;
330    use crate::MailAddr;
331
332    type Nested = EventLayer<u8, EventLayer<u16, User<MailAddr, ()>>>;
333
334    #[test]
335    fn paths_select_exactly_one_layer_without_runtime_search() {
336        let outer = <Nested as InjectEvent<u8, Here>>::inject_at(3);
337        assert_eq!(outer, EventLayer::Owned(3));
338
339        let inner = <Nested as InjectEvent<u16, Inside<Here>>>::inject_at(5);
340        assert_eq!(inner, EventLayer::Inner(EventLayer::Owned(5)));
341    }
342
343    #[test]
344    fn duplicate_payload_types_remain_distinct_ownership_capabilities() {
345        type Duplicate = EventLayer<u8, EventLayer<u8, User<MailAddr, ()>>>;
346
347        let outer = <Duplicate as InjectEvent<u8, Here>>::inject_at(7);
348        let inner = <Duplicate as InjectEvent<u8, Inside<Here>>>::inject_at(7);
349
350        assert_eq!(outer, EventLayer::Owned(7));
351        assert_eq!(inner, EventLayer::Inner(EventLayer::Owned(7)));
352    }
353
354    #[test]
355    fn a_unique_structural_path_is_inferred_at_a_concrete_composition() {
356        let event = <Nested as InjectEvent<u16, _>>::inject_at(9);
357        assert_eq!(event, EventLayer::Inner(EventLayer::Owned(9)));
358    }
359
360    #[test]
361    fn ingress_identity_lifts_without_payload_or_runtime_route_search() {
362        type Inner = EventLayer<u16, User<MailAddr, ()>>;
363        type Outer = EventLayer<u8, Inner>;
364
365        let inner = Ingress::<u16, Here>::new();
366        let outer: Ingress<u16, Inside<Here>> = inner.inside();
367
368        let event: Outer = outer.event(12);
369        assert_eq!(event, EventLayer::Inner(EventLayer::Owned(12)));
370    }
371
372    #[test]
373    #[allow(
374        clippy::clone_on_copy,
375        reason = "the contract test independently exercises both promised construction traits"
376    )]
377    fn ingress_is_a_copyable_equal_capability_with_stable_debug_identity() {
378        let ingress = Ingress::<u16, Here>::new();
379        let copied = ingress;
380        let cloned = ingress.clone();
381
382        assert_eq!(ingress, copied);
383        assert_eq!(ingress, cloned);
384        assert_eq!(format!("{ingress:?}"), "Ingress");
385    }
386}