Skip to main content

behavior_actors/atomic/
diagnostic.rs

1//! One-attempt diagnostic delivery or route-free terminal custody.
2
3use core::convert::Infallible;
4
5use behavior::{
6    ActionItem, EndpointAddress, EstablishedRecipient, ExactDeliveryReason, InterpretationProgress,
7    InterpreterRequest, ItemSettlement, LogicalDeliveryReason, Never, NoReturnToEmitter, Protocol,
8    Recipient, finish_item, prepare_item,
9};
10
11mod sealed {
12    pub trait DiagnosticRoute<Diagnostic> {}
13}
14
15/// One-attempt diagnostic delivery or route-free terminal custody.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum DiagnosticDisposition<Route> {
18    /// Attempt each diagnostic once through this typed route.
19    DeliverTo(Route),
20    /// Transfer each diagnostic directly to terminal custody.
21    Terminate,
22}
23
24impl<Route> DiagnosticDisposition<Route> {
25    /// Attempt each diagnostic once through this typed route.
26    #[must_use]
27    pub const fn deliver_to(route: Route) -> Self {
28        Self::DeliverTo(route)
29    }
30
31    pub(crate) fn action<Diagnostic>(
32        &self,
33        diagnostic: Diagnostic,
34    ) -> DiagnosticAction<Route, Diagnostic>
35    where
36        Route: Clone,
37    {
38        match self {
39            Self::DeliverTo(route) => DiagnosticAction::deliver(route.clone(), diagnostic),
40            Self::Terminate => DiagnosticAction::terminal(diagnostic),
41        }
42    }
43}
44
45impl DiagnosticDisposition<Infallible> {
46    /// Transfer diagnostics directly to terminal custody without a route.
47    #[must_use]
48    pub const fn terminate() -> Self {
49        Self::Terminate
50    }
51}
52
53/// Static rejection vocabulary selected by one diagnostic route family.
54pub trait DiagnosticRoute<Diagnostic>: sealed::DiagnosticRoute<Diagnostic> + Send + Sized {
55    type Rejection: Send;
56    type LogicalProtocols: behavior::BirthProtocolProduct;
57}
58
59impl<P> sealed::DiagnosticRoute<P::Msg> for Recipient<P>
60where
61    P: Protocol,
62    P::Addr: Send,
63    P::Msg: Send,
64{
65}
66
67impl<P> DiagnosticRoute<P::Msg> for Recipient<P>
68where
69    P: Protocol,
70    P::Addr: Send,
71    P::Msg: Send,
72{
73    type Rejection = LogicalDeliveryReason;
74    type LogicalProtocols = behavior::BirthProtocol<P, behavior::NoBirthProtocols>;
75}
76
77impl<P> sealed::DiagnosticRoute<P::Msg> for EstablishedRecipient<P>
78where
79    P: Protocol,
80    P::Addr: EndpointAddress,
81    <P::Addr as EndpointAddress>::Established<P>: Send,
82    P::Msg: Send,
83{
84}
85
86impl<P> DiagnosticRoute<P::Msg> for EstablishedRecipient<P>
87where
88    P: Protocol,
89    P::Addr: EndpointAddress,
90    <P::Addr as EndpointAddress>::Established<P>: Send,
91    P::Msg: Send,
92{
93    type Rejection = ExactDeliveryReason;
94    type LogicalProtocols = behavior::NoBirthProtocols;
95}
96
97impl<Diagnostic> sealed::DiagnosticRoute<Diagnostic> for Infallible where Diagnostic: Send {}
98
99impl<Diagnostic> DiagnosticRoute<Diagnostic> for Infallible
100where
101    Diagnostic: Send,
102{
103    type Rejection = Never;
104    type LogicalProtocols = behavior::NoBirthProtocols;
105}
106
107/// One complete diagnostic selected for delivery or terminal custody.
108///
109/// A route for another diagnostic protocol cannot become an action item:
110///
111/// ```compile_fail,E0271
112/// fn require_action<Item: behavior::ActionItem>(_: Item) {}
113/// let route = behavior::Recipient::<behavior::MessageProtocol<behavior::MailAddr, u8>>::global(behavior::MailAddr(1));
114/// require_action(behavior_actors::atomic::DiagnosticAction::deliver(route, String::from("failed")));
115/// ```
116#[must_use = "a diagnostic action must be interpreted or retained"]
117pub enum DiagnosticAction<Route, Diagnostic> {
118    Deliver {
119        route: Route,
120        diagnostic: Diagnostic,
121    },
122    Terminal {
123        diagnostic: Diagnostic,
124    },
125}
126
127impl<Route, Diagnostic> DiagnosticAction<Route, Diagnostic> {
128    /// Construct one routed diagnostic without cloning its payload.
129    #[must_use]
130    pub const fn deliver(route: Route, diagnostic: Diagnostic) -> Self {
131        Self::Deliver { route, diagnostic }
132    }
133
134    /// Construct one route-free terminal diagnostic.
135    #[must_use]
136    pub const fn terminal(diagnostic: Diagnostic) -> Self {
137        Self::Terminal { diagnostic }
138    }
139}
140
141impl<Route, Diagnostic> InterpreterRequest for DiagnosticAction<Route, Diagnostic>
142where
143    Route: DiagnosticRoute<Diagnostic>,
144{
145    type ReturnToEmitter = NoReturnToEmitter;
146    type LogicalProtocols = Route::LogicalProtocols;
147}
148
149impl<Route, Diagnostic> ActionItem for DiagnosticAction<Route, Diagnostic>
150where
151    Route: DiagnosticRoute<Diagnostic>,
152    Diagnostic: Send,
153{
154    type Custody = (Option<Self>, Option<Self::Reply>);
155    type Input<'a>
156        = &'a mut Option<Self>
157    where
158        Self: 'a;
159    type Reply = ItemSettlement<Self, Self::Accepted, Self::Rejection, Self::Prerequisite>;
160
161    fn prepare_interpretation(
162        progress: &mut Option<InterpretationProgress<Self, Self::Custody, Self::Reply>>,
163    ) {
164        prepare_item::<Self>(progress);
165    }
166
167    fn interpretation_input<'a>(
168        custody: &'a mut Self::Custody,
169    ) -> Option<(Self::Input<'a>, &'a mut Option<Self::Reply>)>
170    where
171        Self: 'a,
172    {
173        match custody {
174            (input @ Some(_), received @ None) => Some((input, received)),
175            _ => None,
176        }
177    }
178
179    fn finish_interpretation(
180        progress: &mut Option<InterpretationProgress<Self, Self::Custody, Self::Reply>>,
181    ) {
182        finish_item::<Self>(progress);
183    }
184
185    type Accepted = DiagnosticAccepted<Diagnostic>;
186    type Rejection = Route::Rejection;
187    type Prerequisite = Never;
188
189    fn retain_accepted(accepted: Self::Accepted) -> Option<Self::Accepted> {
190        match accepted {
191            DiagnosticAccepted::Delivered => None,
192            DiagnosticAccepted::Terminal(diagnostic) => {
193                Some(DiagnosticAccepted::Terminal(diagnostic))
194            }
195        }
196    }
197}
198
199/// Complete accepted disposition of one diagnostic action.
200#[must_use = "terminal diagnostic custody must be retained"]
201pub enum DiagnosticAccepted<Diagnostic> {
202    Delivered,
203    Terminal(Diagnostic),
204}
205
206impl<Diagnostic> DiagnosticAccepted<Diagnostic> {
207    /// Record one accepted routed delivery.
208    #[must_use]
209    pub const fn delivered() -> Self {
210        Self::Delivered
211    }
212
213    /// Transfer one complete diagnostic into terminal custody.
214    #[must_use]
215    pub const fn terminal(diagnostic: Diagnostic) -> Self {
216        Self::Terminal(diagnostic)
217    }
218}