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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use std::fmt;

use bigerror::reportable;
use tokio::time::Instant;
use tracing::error;
use uuid::Uuid;

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 manager::{
    HashKind, RexBuilder, Signal, SignalExt, SignalQueue, SmContext, StateMachine, StateMachineExt,
    StateMachineManager,
};
pub use notification::{
    GetTopic, Notification, NotificationManager, NotificationProcessor, Operation, Request,
    RequestInner, RexMessage, RexTopic, Subscriber, UnaryRequest,
};

/// 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 {
    fn get_kind(&self) -> &dyn Kind<State = Self>;
    fn fail(&mut self)
    where
        Self: Sized,
    {
        *self = self.get_kind().failed_state();
    }
    fn complete(&mut self)
    where
        Self: Sized,
    {
        *self = self.get_kind().completed_state();
    }
    fn is_completed(&self) -> bool
    where
        Self: Sized,
        for<'a> &'a Self: PartialEq<&'a Self>,
    {
        self == &self.get_kind().completed_state()
    }

    fn is_failed(&self) -> bool
    where
        Self: Sized,
        for<'a> &'a Self: PartialEq<&'a Self>,
    {
        self == &self.get_kind().failed_state()
    }
    fn is_new(&self) -> bool
    where
        Self: Sized,
        for<'a> &'a Self: PartialEq<&'a Self>,
    {
        self == &self.get_kind().new_state()
    }

    /// represents a state that will no longer change
    fn is_terminal(&self) -> bool
    where
        Self: Sized,
    {
        self.is_failed() || self.is_completed()
    }

    /// `&dyn Kind<State = Self>` cannot do direct partial comparison
    /// due to type opacity
    /// so State::new_state(self) is called to allow a vtable lookup
    fn kind_eq(&self, kind: &dyn Kind<State = Self>) -> bool
    where
        Self: Sized,
    {
        self.get_kind().new_state() == kind.new_state()
    }
}

/// 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 {
    type State: State;
    fn new_state(&self) -> Self::State;
    fn failed_state(&self) -> Self::State;
    fn completed_state(&self) -> Self::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
///            Topic
///            ::
/// Kind -> Rex::Message
/// ::      ::      ::
/// State   Input   Topic
/// ```
pub trait Rex: Kind + HashKind {
    type Input: Send + Sync + 'static + fmt::Debug;
    type Message: RexMessage;
    fn state_input(&self, state: <Self as Kind>::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.uuid)
    }
}

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

    pub fn new_rand(kind: K) -> Self {
        Self::new(kind, Uuid::new_v4())
    }
    // for testing purposes, easily distinguish UUIDs
    // by numerical value
    #[cfg(any(feature = "test", test))]
    pub fn new_with_u128(kind: K, v: u128) -> Self {
        Self {
            kind,
            uuid: Uuid::from_u128(v),
        }
    }
}

#[cfg(any(feature = "test", test))]
pub trait TestDefault {
    fn test_default() -> Self;
}

#[derive(Debug, thiserror::Error)]
#[error("StateMachineError")]
pub struct StateMachineError;
reportable!(StateMachineError);