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