Skip to main content

behavior/
spec.rs

1//! Intent-facing typestate composition. Every method immediately builds a
2//! concrete pure behavior; there is no separate intent representation.
3
4use std::time::Duration;
5
6use tokio::time::Instant;
7
8use crate::behavior::{Address, Behavior, BirthMode, Births};
9use crate::deadlined::{At, AtReaction};
10use crate::protocol::TimerId;
11use crate::receive_timeout::{ReceiveTimeout, ReceiveTimeoutReaction};
12use crate::shutdown::{FinalizeOnShutdown, ShutdownReaction, StopOnShutdown};
13use crate::stashing::{StashRoute, Stashing};
14use crate::supervision::{RestartPolicy, Strategy, Supervising, SupervisionFailureReaction};
15use crate::verdict::Never;
16use crate::watching::{LinkReaction, Watching};
17use crate::{Actions, Base, Fsm, Move, SendAlgebra, State};
18
19const DEFAULT_STRATEGY: Strategy = Strategy::OneForOne;
20const DEFAULT_POLICY: RestartPolicy = RestartPolicy::Transient;
21const DEFAULT_BUDGET: (u32, Duration) = (1, Duration::from_secs(5));
22
23fn identity_nonce<N: From<u64>>(index: usize) -> N {
24    N::from(u64::try_from(index).expect("fleet index fits u64"))
25}
26
27pub struct Spec<B> {
28    behavior: B,
29    next_timer: u64,
30}
31
32impl<S: State<O, Br, E>, O, Br: BirthMode, E> Spec<Base<S, O, Br, E>> {
33    #[must_use]
34    pub fn new(state: S) -> Self {
35        Self {
36            behavior: Base::new(state),
37            next_timer: 0,
38        }
39    }
40}
41
42impl<A, S, M, P, E> Spec<Fsm<A, S, M, P, E>>
43where
44    A: Address,
45    P: Copy + PartialEq,
46{
47    #[must_use]
48    pub fn machine(state: S, phase: P, on: fn(P, &mut S, &M) -> Result<Move<P>, E>) -> Self {
49        Self {
50            behavior: Fsm::new(state, phase, on),
51            next_timer: 0,
52        }
53    }
54}
55
56impl<B: Behavior> Spec<B> {
57    #[must_use]
58    pub fn from_behavior(behavior: B) -> Self {
59        Self {
60            behavior,
61            next_timer: 0,
62        }
63    }
64
65    #[must_use]
66    pub fn build(self) -> B {
67        self.behavior
68    }
69
70    #[must_use]
71    pub fn behavior(&self) -> &B {
72        &self.behavior
73    }
74
75    /// Stop normally when a typed shutdown request is folded.
76    #[must_use]
77    pub fn stop_on_shutdown(self) -> Spec<StopOnShutdown<B>> {
78        Spec {
79            behavior: StopOnShutdown::new(self.behavior),
80            next_timer: self.next_timer,
81        }
82    }
83
84    /// Apply one final pure fold, retain its sends and creations, and stop
85    /// normally regardless of the fold's become verdict.
86    #[must_use]
87    pub fn finalize_on_shutdown(
88        self,
89        finalize: ShutdownReaction<B>,
90    ) -> Spec<FinalizeOnShutdown<B>> {
91        Spec {
92            behavior: FinalizeOnShutdown::new(self.behavior, finalize),
93            next_timer: self.next_timer,
94        }
95    }
96
97    /// Observe a peer and apply a pure reaction when it stops.
98    #[must_use]
99    pub fn watch(self, peer: B::Addr, on_stopped: LinkReaction<B>) -> Spec<Watching<B>> {
100        Spec {
101            behavior: Watching::new(self.behavior, peer, on_stopped),
102            next_timer: self.next_timer,
103        }
104    }
105
106    /// Apply a pure reaction when the given absolute time is reached.
107    ///
108    /// # Panics
109    ///
110    /// Panics if one specification composes more than `u64::MAX` timer
111    /// capabilities.
112    #[must_use]
113    pub fn at(self, when: Option<Instant>, on_reached: AtReaction<B>) -> Spec<At<B>> {
114        Spec {
115            behavior: At::new(self.behavior, TimerId(self.next_timer), when, on_reached),
116            next_timer: self
117                .next_timer
118                .checked_add(1)
119                .expect("timer identity exhausted"),
120        }
121    }
122
123    /// Notify the behavior once after an idle period containing no successful
124    /// user communication.
125    ///
126    /// Initialization and each successful continuing user fold emit a relative
127    /// schedule. Service events never reset inactivity. A matching delivery is
128    /// consumed before `on_elapsed` runs, and a continuing reaction remains
129    /// unarmed until another successful continuing user communication.
130    ///
131    /// # Panics
132    ///
133    /// Panics if one specification composes more than `u64::MAX` timer
134    /// capabilities.
135    #[must_use]
136    pub fn receive_timeout(
137        self,
138        after: Duration,
139        on_elapsed: ReceiveTimeoutReaction<B>,
140    ) -> Spec<ReceiveTimeout<B>> {
141        Spec {
142            behavior: ReceiveTimeout::new(
143                self.behavior,
144                TimerId(self.next_timer),
145                after,
146                on_elapsed,
147            ),
148            next_timer: self
149                .next_timer
150                .checked_add(1)
151                .expect("timer identity exhausted"),
152        }
153    }
154
155    /// Hold messages selected by `route` and replay them on `Release`.
156    #[must_use]
157    pub fn stash(self, route: fn(&B::Msg) -> StashRoute) -> Spec<Stashing<B>>
158    where
159        B: Behavior<Ph = Never>,
160    {
161        Spec {
162            behavior: Stashing::new(self.behavior, route),
163            next_timer: self.next_timer,
164        }
165    }
166
167    /// Create a supervised child topology. Concrete proxy and monitor types
168    /// remain hidden in the returned typestate.
169    #[must_use]
170    pub fn children<C>(self, fleet: (usize, fn(usize) -> C)) -> Spec<Supervising<B, C>>
171    where
172        B: Behavior<Birth = Births<C>>,
173        C: Behavior<Ph = Never, Addr = B::Addr>,
174        <B::Addr as Address>::Nonce: From<u64>,
175    {
176        self.children_with_nonces(identity_nonce, fleet.0, fleet.1)
177    }
178
179    #[must_use]
180    pub fn children_with_nonces<C>(
181        self,
182        nonces: fn(usize) -> <B::Addr as Address>::Nonce,
183        count: usize,
184        build: fn(usize) -> C,
185    ) -> Spec<Supervising<B, C>>
186    where
187        B: Behavior<Birth = Births<C>>,
188        C: Behavior<Ph = Never, Addr = B::Addr>,
189    {
190        Spec {
191            behavior: Supervising::new(
192                self.behavior,
193                nonces,
194                count,
195                build,
196                DEFAULT_STRATEGY,
197                DEFAULT_POLICY,
198                DEFAULT_BUDGET.0,
199                DEFAULT_BUDGET.1,
200            ),
201            next_timer: self.next_timer,
202        }
203    }
204}
205
206impl<B, C> Spec<Supervising<B, C>>
207where
208    B: Behavior<Birth = Births<C>>,
209    C: Behavior<Ph = Never, Addr = B::Addr>,
210{
211    #[must_use]
212    pub fn restart(self, strategy: Strategy) -> Self {
213        Self {
214            behavior: self.behavior.with_strategy(strategy),
215            next_timer: self.next_timer,
216        }
217    }
218
219    #[must_use]
220    pub fn when(self, policy: RestartPolicy) -> Self {
221        Self {
222            behavior: self.behavior.with_policy(policy),
223            next_timer: self.next_timer,
224        }
225    }
226
227    #[must_use]
228    pub fn within(self, maximum: u32, window: Duration) -> Self {
229        Self {
230            behavior: self.behavior.with_budget(maximum, window),
231            next_timer: self.next_timer,
232        }
233    }
234
235    /// Apply a pure reaction when supervision can no longer preserve its
236    /// child topology.
237    #[must_use]
238    pub fn on_supervision_failure(self, reaction: SupervisionFailureReaction<B>) -> Self {
239        Self {
240            behavior: self.behavior.with_failure_reaction(reaction),
241            next_timer: self.next_timer,
242        }
243    }
244}
245
246impl<B, A, Ph, Sends, Br> Behavior for Spec<B>
247where
248    A: Address + Send,
249    Sends: SendAlgebra,
250    Br: BirthMode,
251    B: Behavior<Addr = A, Ph = Ph, Sends = Sends, Birth = Br> + Send,
252    A::Nonce: Send,
253    B::Msg: Send,
254    B::Event: Send,
255{
256    type Addr = A;
257    type Msg = B::Msg;
258    type Event = B::Event;
259    type Sends = Sends;
260    type Ph = Ph;
261    type Error = B::Error;
262    type Birth = Br;
263
264    async fn init(&mut self) -> Result<Actions<A, Ph, Sends, Br>, B::Error> {
265        self.behavior.init().await
266    }
267
268    async fn step(&mut self, event: B::Event) -> Result<Actions<A, Ph, Sends, Br>, B::Error> {
269        self.behavior.step(event).await
270    }
271}