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