use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum trace_statusState {
Outgoing,
Process,
Pending,
Sent,
Open,
Reply,
Bounce,
Error,
Cancel,
}
impl Default for trace_statusState {
fn default() -> Self {
Self::Outgoing
}
}
impl std::fmt::Display for trace_statusState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Outgoing => write!(f, "outgoing"),
Self::Process => write!(f, "process"),
Self::Pending => write!(f, "pending"),
Self::Sent => write!(f, "sent"),
Self::Open => write!(f, "open"),
Self::Reply => write!(f, "reply"),
Self::Bounce => write!(f, "bounce"),
Self::Error => write!(f, "error"),
Self::Cancel => write!(f, "cancel"),
}
}
}
impl FromStr for trace_statusState {
type Err = StateMachineError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"outgoing" => Ok(Self::Outgoing),
"process" => Ok(Self::Process),
"pending" => Ok(Self::Pending),
"sent" => Ok(Self::Sent),
"open" => Ok(Self::Open),
"reply" => Ok(Self::Reply),
"bounce" => Ok(Self::Bounce),
"error" => Ok(Self::Error),
"cancel" => Ok(Self::Cancel),
_ => Err(StateMachineError::InvalidState(s.to_string())),
}
}
}
impl trace_statusState {
pub fn is_initial(&self) -> bool {
matches!(self, Self::Outgoing)
}
pub fn is_final(&self) -> bool {
matches!(self, Self::Reply | Self::Bounce | Self::Error | Self::Cancel)
}
pub fn all() -> Vec<Self> {
vec![
Self::Outgoing,
Self::Process,
Self::Pending,
Self::Sent,
Self::Open,
Self::Reply,
Self::Bounce,
Self::Error,
Self::Cancel,
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum trace_statusTransition {
SetProcess,
SetPending,
SetSent,
SetOpened,
SetReplied,
SetBounced,
SetBouncedSms,
SetFailed,
SetCanceled,
}
impl std::fmt::Display for trace_statusTransition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::SetProcess => write!(f, "set_process"),
Self::SetPending => write!(f, "set_pending"),
Self::SetSent => write!(f, "set_sent"),
Self::SetOpened => write!(f, "set_opened"),
Self::SetReplied => write!(f, "set_replied"),
Self::SetBounced => write!(f, "set_bounced"),
Self::SetBouncedSms => write!(f, "set_bounced_sms"),
Self::SetFailed => write!(f, "set_failed"),
Self::SetCanceled => write!(f, "set_canceled"),
}
}
}
impl FromStr for trace_statusTransition {
type Err = StateMachineError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"set_process" => Ok(Self::SetProcess),
"set_pending" => Ok(Self::SetPending),
"set_sent" => Ok(Self::SetSent),
"set_opened" => Ok(Self::SetOpened),
"set_replied" => Ok(Self::SetReplied),
"set_bounced" => Ok(Self::SetBounced),
"set_bounced_sms" => Ok(Self::SetBouncedSms),
"set_failed" => Ok(Self::SetFailed),
"set_canceled" => Ok(Self::SetCanceled),
_ => Err(StateMachineError::InvalidTransition(s.to_string())),
}
}
}
impl trace_statusTransition {
pub fn target_state(&self) -> trace_statusState {
match self {
Self::SetProcess => trace_statusState::Process,
Self::SetPending => trace_statusState::Pending,
Self::SetSent => trace_statusState::Sent,
Self::SetOpened => trace_statusState::Open,
Self::SetReplied => trace_statusState::Reply,
Self::SetBounced => trace_statusState::Bounce,
Self::SetBouncedSms => trace_statusState::Bounce,
Self::SetFailed => trace_statusState::Error,
Self::SetCanceled => trace_statusState::Cancel,
}
}
pub fn all() -> Vec<Self> {
vec![
Self::SetProcess,
Self::SetPending,
Self::SetSent,
Self::SetOpened,
Self::SetReplied,
Self::SetBounced,
Self::SetBouncedSms,
Self::SetFailed,
Self::SetCanceled,
]
}
pub fn allowed_roles(&self) -> &'static [&'static str] {
match self {
Self::SetProcess => &[],
Self::SetPending => &[],
Self::SetSent => &[],
Self::SetOpened => &[],
Self::SetReplied => &[],
Self::SetBounced => &[],
Self::SetBouncedSms => &[],
Self::SetFailed => &[],
Self::SetCanceled => &[],
}
}
}
use super::StateMachineError;
#[derive(Debug, Clone)]
pub struct trace_statusStateMachine {
current_state: trace_statusState,
}
impl trace_statusStateMachine {
pub fn new() -> Self {
Self {
current_state: trace_statusState::default(),
}
}
pub fn from_state(state: trace_statusState) -> Self {
Self { current_state: state }
}
pub fn current_state(&self) -> trace_statusState {
self.current_state
}
pub fn can_transition(&self, transition: trace_statusTransition) -> bool {
if matches!(self.current_state, trace_statusState::Reply | trace_statusState::Bounce | trace_statusState::Error | trace_statusState::Cancel) {
return false;
}
match (self.current_state, transition) {
(trace_statusState::Outgoing, trace_statusTransition::SetProcess) => true,
(trace_statusState::Outgoing, trace_statusTransition::SetPending) => true,
(trace_statusState::Process, trace_statusTransition::SetPending) => true,
(trace_statusState::Outgoing, trace_statusTransition::SetSent) => true,
(trace_statusState::Process, trace_statusTransition::SetSent) => true,
(trace_statusState::Pending, trace_statusTransition::SetSent) => true,
(trace_statusState::Outgoing, trace_statusTransition::SetOpened) => true,
(trace_statusState::Sent, trace_statusTransition::SetOpened) => true,
(trace_statusState::Process, trace_statusTransition::SetOpened) => true,
(trace_statusState::Pending, trace_statusTransition::SetOpened) => true,
(trace_statusState::Outgoing, trace_statusTransition::SetReplied) => true,
(trace_statusState::Sent, trace_statusTransition::SetReplied) => true,
(trace_statusState::Open, trace_statusTransition::SetReplied) => true,
(trace_statusState::Process, trace_statusTransition::SetReplied) => true,
(trace_statusState::Pending, trace_statusTransition::SetReplied) => true,
(trace_statusState::Outgoing, trace_statusTransition::SetBounced) => true,
(trace_statusState::Sent, trace_statusTransition::SetBounced) => true,
(trace_statusState::Open, trace_statusTransition::SetBounced) => true,
(trace_statusState::Process, trace_statusTransition::SetBounced) => true,
(trace_statusState::Pending, trace_statusTransition::SetBounced) => true,
(trace_statusState::Outgoing, trace_statusTransition::SetBouncedSms) => true,
(trace_statusState::Process, trace_statusTransition::SetBouncedSms) => true,
(trace_statusState::Pending, trace_statusTransition::SetBouncedSms) => true,
(trace_statusState::Outgoing, trace_statusTransition::SetFailed) => true,
(trace_statusState::Sent, trace_statusTransition::SetFailed) => true,
(trace_statusState::Process, trace_statusTransition::SetFailed) => true,
(trace_statusState::Pending, trace_statusTransition::SetFailed) => true,
(trace_statusState::Outgoing, trace_statusTransition::SetCanceled) => true,
_ => false,
}
}
pub fn can_transition_with_role(&self, transition: trace_statusTransition, role: &str) -> bool {
if !self.can_transition(transition) {
return false;
}
let allowed_roles = transition.allowed_roles();
if allowed_roles.is_empty() {
return true; }
allowed_roles.iter().any(|r| *r == role || *r == "*")
}
pub fn transition(&mut self, transition: trace_statusTransition) -> Result<trace_statusState, StateMachineError> {
if !self.can_transition(transition) {
return Err(StateMachineError::TransitionNotAllowed {
transition: transition.to_string(),
from: self.current_state.to_string(),
});
}
self.current_state = transition.target_state();
Ok(self.current_state)
}
pub fn transition_with_role(&mut self, transition: trace_statusTransition, role: &str) -> Result<trace_statusState, StateMachineError> {
if !self.can_transition(transition) {
return Err(StateMachineError::TransitionNotAllowed {
transition: transition.to_string(),
from: self.current_state.to_string(),
});
}
if !self.can_transition_with_role(transition, role) {
return Err(StateMachineError::RoleNotAuthorized {
role: role.to_string(),
transition: transition.to_string(),
});
}
self.current_state = transition.target_state();
Ok(self.current_state)
}
pub fn available_transitions(&self) -> Vec<trace_statusTransition> {
trace_statusTransition::all()
.into_iter()
.filter(|t| self.can_transition(*t))
.collect()
}
pub fn available_transitions_for_role(&self, role: &str) -> Vec<trace_statusTransition> {
trace_statusTransition::all()
.into_iter()
.filter(|t| self.can_transition_with_role(*t, role))
.collect()
}
pub fn transition_to_state(&mut self, target: trace_statusState) -> Result<trace_statusState, StateMachineError> {
let valid = trace_statusTransition::all().into_iter()
.filter(|t| self.can_transition(*t))
.find(|t| t.target_state() == target);
match valid {
Some(t) => self.transition(t),
None => Err(StateMachineError::TransitionNotAllowed {
transition: target.to_string(),
from: self.current_state.to_string(),
}),
}
}
}
impl Default for trace_statusStateMachine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_initial_state() {
let sm = trace_statusStateMachine::new();
assert_eq!(sm.current_state(), trace_statusState::Outgoing);
assert!(sm.current_state().is_initial());
}
#[test]
fn test_valid_transition() {
let mut sm = trace_statusStateMachine::from_state(trace_statusState::Outgoing);
assert!(sm.can_transition(trace_statusTransition::SetProcess));
let result = sm.transition(trace_statusTransition::SetProcess);
assert!(result.is_ok());
assert_eq!(sm.current_state(), trace_statusState::Process);
}
#[test]
fn test_invalid_transition() {
let mut sm = trace_statusStateMachine::from_state(trace_statusState::Process);
let result = sm.transition(trace_statusTransition::SetProcess);
assert!(result.is_err());
}
#[test]
fn test_state_parsing() {
let state: trace_statusState = "outgoing".parse().unwrap();
assert_eq!(state, trace_statusState::Outgoing);
}
#[test]
fn test_available_transitions() {
let sm = trace_statusStateMachine::new();
let available = sm.available_transitions();
assert!(!available.is_empty() || sm.current_state().is_final());
}
}