rex/
lib.rs

1#![allow(clippy::module_name_repetitions)]
2use std::fmt;
3
4use bigerror::ThinContext;
5use tokio::time::Instant;
6use uuid::Uuid;
7
8pub mod builder;
9pub mod ingress;
10pub mod manager;
11pub mod node;
12pub mod notification;
13pub mod queue;
14pub mod storage;
15pub mod timeout;
16
17#[cfg(test)]
18mod test_support;
19
20pub use builder::RexBuilder;
21pub use manager::{
22    HashKind, Signal, SignalExt, SignalQueue, SmContext, StateMachine, StateMachineExt,
23    StateMachineManager,
24};
25pub use notification::{
26    GetTopic, Notification, NotificationManager, NotificationProcessor, NotificationQueue,
27    Operation, Request, RequestInner, RexMessage, RexTopic, Subscriber, UnaryRequest,
28};
29pub use timeout::Timeout;
30
31/// A trait for types representing state machine lifecycles. These types should be field-less
32/// enumerations or enumerations whose variants only contain field-less enumerations; note that
33/// `Copy` is a required supertrait.
34pub trait State: fmt::Debug + Send + PartialEq + Copy {
35    type Input: Send + Sync + 'static + fmt::Debug;
36}
37
38/// Acts as a discriminant between various [`State`] enumerations, similar to
39/// [`std::mem::Discriminant`].
40/// Used to define the scope for [`Signal`]s cycled through a [`StateMachineManager`].
41pub trait Kind: fmt::Debug + Send + Sized {
42    type State: State<Input = Self::Input> + AsRef<Self>;
43    type Input: Send + Sync + 'static + fmt::Debug;
44
45    fn new_state(&self) -> Self::State;
46    fn failed_state(&self) -> Self::State;
47    fn completed_state(&self) -> Self::State;
48    // /// represents a state that will no longer change
49    fn is_terminal(state: Self::State) -> bool {
50        let kind = state.as_ref();
51        kind.completed_state() == state || kind.failed_state() == state
52    }
53}
54
55/// Titular trait of the library that enables Hierarchical State Machine (HSM for short) behaviour.
56/// Makes sending [`Signal`]s (destined to become a [`StateMachine`]'s input)
57/// and [`Notification`]s (a [`NotificationProcessor`]'s input) possible.
58///
59/// The [`Rex`] trait defines the _scope_ of interaction that exists between one or more
60/// [`StateMachine`]s and zero or more [`NotificationProcessor`]s.
61/// Below is a diagram displaying the associated
62/// type hierarchy defined by validly implementing the [`Kind`] and [`Rex`] traits,
63/// double colons`::` directed down and right represent type associations:
64/// ```text
65///
66/// Kind -> Rex::Message
67///   ::              ::
68///   State::Input    Topic
69/// ```
70pub trait Rex: Kind + HashKind
71where
72    Self::State: AsRef<Self>,
73{
74    type Message: RexMessage;
75    fn state_input(&self, state: Self::State) -> Option<Self::Input>;
76    fn timeout_input(&self, instant: Instant) -> Option<Self::Input>;
77}
78
79/// Implements [`node::Node`] `Id` generics by holding a [`Kind`] field
80/// and a [`Uuid`] to be used as a _distinguishing_ identifier
81#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
82pub struct StateId<K: Kind> {
83    pub kind: K,
84    pub uuid: Uuid,
85}
86
87impl<K> std::ops::Deref for StateId<K>
88where
89    K: Kind,
90{
91    type Target = K;
92
93    fn deref(&self) -> &Self::Target {
94        &self.kind
95    }
96}
97
98impl<K> fmt::Display for StateId<K>
99where
100    K: Kind,
101{
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(
104            f,
105            "{:?}<{}>",
106            self.kind,
107            (!self.is_nil())
108                .then(|| bs58::encode(self.uuid).into_string())
109                .unwrap_or_else(|| "NIL".to_string())
110        )
111    }
112}
113
114impl<K: Kind> StateId<K> {
115    pub const fn new(kind: K, uuid: Uuid) -> Self {
116        Self { kind, uuid }
117    }
118
119    pub fn new_rand(kind: K) -> Self {
120        Self::new(kind, Uuid::new_v4())
121    }
122
123    pub const fn nil(kind: K) -> Self {
124        Self::new(kind, Uuid::nil())
125    }
126    pub fn is_nil(&self) -> bool {
127        self.uuid == Uuid::nil()
128    }
129    // for testing purposes, easily distinguish UUIDs
130    // by numerical value
131    #[cfg(test)]
132    pub const fn new_with_u128(kind: K, v: u128) -> Self {
133        Self {
134            kind,
135            uuid: Uuid::from_u128(v),
136        }
137    }
138}
139
140#[derive(ThinContext)]
141pub struct RexError;