rex/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#![allow(clippy::module_name_repetitions)]
use std::fmt;

use bigerror::ThinContext;
use tokio::time::Instant;
use uuid::Uuid;

pub mod builder;
pub mod ingress;
pub mod manager;
pub mod node;
pub mod notification;
pub mod queue;
pub mod storage;
pub mod timeout;

#[cfg(test)]
mod test_support;

pub use builder::RexBuilder;
pub use manager::{
    HashKind, Signal, SignalExt, SignalQueue, SmContext, StateMachine, StateMachineExt,
    StateMachineManager,
};
pub use notification::{
    GetTopic, Notification, NotificationManager, NotificationProcessor, NotificationQueue,
    Operation, Request, RequestInner, RexMessage, RexTopic, Subscriber, UnaryRequest,
};
pub use timeout::Timeout;

/// A trait for types representing state machine lifecycles. These types should be field-less
/// enumerations or enumerations whose variants only contain field-less enumerations; note that
/// `Copy` is a required supertrait.
pub trait State: fmt::Debug + Send + PartialEq + Copy {
    type Input: Send + Sync + 'static + fmt::Debug;
}

/// Acts as a discriminant between various [`State`] enumerations, similar to
/// [`std::mem::Discriminant`].
/// Used to define the scope for [`Signal`]s cycled through a [`StateMachineManager`].
pub trait Kind: fmt::Debug + Send + Sized {
    type State: State<Input = Self::Input> + AsRef<Self>;
    type Input: Send + Sync + 'static + fmt::Debug;

    fn new_state(&self) -> Self::State;
    fn failed_state(&self) -> Self::State;
    fn completed_state(&self) -> Self::State;
    // /// represents a state that will no longer change
    fn is_terminal(state: Self::State) -> bool {
        let kind = state.as_ref();
        kind.completed_state() == state || kind.failed_state() == state
    }
}

/// Titular trait of the library that enables Hierarchical State Machine (HSM for short) behaviour.
/// Makes sending [`Signal`]s (destined to become a [`StateMachine`]'s input)
/// and [`Notification`]s (a [`NotificationProcessor`]'s input) possible.
///
/// The [`Rex`] trait defines the _scope_ of interaction that exists between one or more
/// [`StateMachine`]s and zero or more [`NotificationProcessor`]s.
/// Below is a diagram displaying the associated
/// type hierarchy defined by validly implementing the [`Kind`] and [`Rex`] traits,
/// double colons`::` directed down and right represent type associations:
/// ```text
///
/// Kind -> Rex::Message
///   ::              ::
///   State::Input    Topic
/// ```
pub trait Rex: Kind + HashKind
where
    Self::State: AsRef<Self>,
{
    type Message: RexMessage;
    fn state_input(&self, state: Self::State) -> Option<Self::Input>;
    fn timeout_input(&self, instant: Instant) -> Option<Self::Input>;
}

/// Implements [`node::Node`] `Id` generics by holding a [`Kind`] field
/// and a [`Uuid`] to be used as a _distinguishing_ identifier
#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
pub struct StateId<K: Kind> {
    pub kind: K,
    pub uuid: Uuid,
}

impl<K> std::ops::Deref for StateId<K>
where
    K: Kind,
{
    type Target = K;

    fn deref(&self) -> &Self::Target {
        &self.kind
    }
}

impl<K> fmt::Display for StateId<K>
where
    K: Kind,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{:?}<{}>",
            self.kind,
            (!self.is_nil())
                .then(|| bs58::encode(self.uuid).into_string())
                .unwrap_or_else(|| "NIL".to_string())
        )
    }
}

impl<K: Kind> StateId<K> {
    pub const fn new(kind: K, uuid: Uuid) -> Self {
        Self { kind, uuid }
    }

    pub fn new_rand(kind: K) -> Self {
        Self::new(kind, Uuid::new_v4())
    }

    pub const fn nil(kind: K) -> Self {
        Self::new(kind, Uuid::nil())
    }
    pub fn is_nil(&self) -> bool {
        self.uuid == Uuid::nil()
    }
    // for testing purposes, easily distinguish UUIDs
    // by numerical value
    #[cfg(test)]
    pub const fn new_with_u128(kind: K, v: u128) -> Self {
        Self {
            kind,
            uuid: Uuid::from_u128(v),
        }
    }
}

#[derive(ThinContext)]
pub struct RexError;