use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum mailing_stateState {
Draft,
InQueue,
Sending,
Done,
}
impl Default for mailing_stateState {
fn default() -> Self {
Self::Draft
}
}
impl std::fmt::Display for mailing_stateState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Draft => write!(f, "draft"),
Self::InQueue => write!(f, "in_queue"),
Self::Sending => write!(f, "sending"),
Self::Done => write!(f, "done"),
}
}
}
impl FromStr for mailing_stateState {
type Err = StateMachineError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"draft" => Ok(Self::Draft),
"in_queue" => Ok(Self::InQueue),
"sending" => Ok(Self::Sending),
"done" => Ok(Self::Done),
_ => Err(StateMachineError::InvalidState(s.to_string())),
}
}
}
impl mailing_stateState {
pub fn is_initial(&self) -> bool {
matches!(self, Self::Draft)
}
pub fn is_final(&self) -> bool {
matches!(self, Self::Done)
}
pub fn all() -> Vec<Self> {
vec![
Self::Draft,
Self::InQueue,
Self::Sending,
Self::Done,
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum mailing_stateTransition {
Launch,
Pickup,
Complete,
CompleteEmpty,
Cancel,
RetryFailed,
}
impl std::fmt::Display for mailing_stateTransition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Launch => write!(f, "launch"),
Self::Pickup => write!(f, "pickup"),
Self::Complete => write!(f, "complete"),
Self::CompleteEmpty => write!(f, "complete_empty"),
Self::Cancel => write!(f, "cancel"),
Self::RetryFailed => write!(f, "retry_failed"),
}
}
}
impl FromStr for mailing_stateTransition {
type Err = StateMachineError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"launch" => Ok(Self::Launch),
"pickup" => Ok(Self::Pickup),
"complete" => Ok(Self::Complete),
"complete_empty" => Ok(Self::CompleteEmpty),
"cancel" => Ok(Self::Cancel),
"retry_failed" => Ok(Self::RetryFailed),
_ => Err(StateMachineError::InvalidTransition(s.to_string())),
}
}
}
impl mailing_stateTransition {
pub fn target_state(&self) -> mailing_stateState {
match self {
Self::Launch => mailing_stateState::InQueue,
Self::Pickup => mailing_stateState::Sending,
Self::Complete => mailing_stateState::Done,
Self::CompleteEmpty => mailing_stateState::Done,
Self::Cancel => mailing_stateState::Draft,
Self::RetryFailed => mailing_stateState::InQueue,
}
}
pub fn all() -> Vec<Self> {
vec![
Self::Launch,
Self::Pickup,
Self::Complete,
Self::CompleteEmpty,
Self::Cancel,
Self::RetryFailed,
]
}
pub fn allowed_roles(&self) -> &'static [&'static str] {
match self {
Self::Launch => &[],
Self::Pickup => &[],
Self::Complete => &[],
Self::CompleteEmpty => &[],
Self::Cancel => &[],
Self::RetryFailed => &[],
}
}
}
use super::StateMachineError;
#[derive(Debug, Clone)]
pub struct mailing_stateStateMachine {
current_state: mailing_stateState,
}
impl mailing_stateStateMachine {
pub fn new() -> Self {
Self {
current_state: mailing_stateState::default(),
}
}
pub fn from_state(state: mailing_stateState) -> Self {
Self { current_state: state }
}
pub fn current_state(&self) -> mailing_stateState {
self.current_state
}
pub fn can_transition(&self, transition: mailing_stateTransition) -> bool {
match (self.current_state, transition) {
(mailing_stateState::Draft, mailing_stateTransition::Launch) => true,
(mailing_stateState::InQueue, mailing_stateTransition::Pickup) => true,
(mailing_stateState::Sending, mailing_stateTransition::Complete) => true,
(mailing_stateState::InQueue, mailing_stateTransition::CompleteEmpty) => true,
(mailing_stateState::InQueue, mailing_stateTransition::Cancel) => true,
(mailing_stateState::Sending, mailing_stateTransition::Cancel) => true,
(mailing_stateState::Done, mailing_stateTransition::Cancel) => true,
(mailing_stateState::Done, mailing_stateTransition::RetryFailed) => true,
_ => false,
}
}
pub fn can_transition_with_role(&self, transition: mailing_stateTransition, 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: mailing_stateTransition) -> Result<mailing_stateState, 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: mailing_stateTransition, role: &str) -> Result<mailing_stateState, 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<mailing_stateTransition> {
mailing_stateTransition::all()
.into_iter()
.filter(|t| self.can_transition(*t))
.collect()
}
pub fn available_transitions_for_role(&self, role: &str) -> Vec<mailing_stateTransition> {
mailing_stateTransition::all()
.into_iter()
.filter(|t| self.can_transition_with_role(*t, role))
.collect()
}
pub fn transition_to_state(&mut self, target: mailing_stateState) -> Result<mailing_stateState, StateMachineError> {
let valid = mailing_stateTransition::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 mailing_stateStateMachine {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_initial_state() {
let sm = mailing_stateStateMachine::new();
assert_eq!(sm.current_state(), mailing_stateState::Draft);
assert!(sm.current_state().is_initial());
}
#[test]
fn test_valid_transition() {
let mut sm = mailing_stateStateMachine::from_state(mailing_stateState::Draft);
assert!(sm.can_transition(mailing_stateTransition::Launch));
let result = sm.transition(mailing_stateTransition::Launch);
assert!(result.is_ok());
assert_eq!(sm.current_state(), mailing_stateState::InQueue);
}
#[test]
fn test_invalid_transition() {
let mut sm = mailing_stateStateMachine::from_state(mailing_stateState::Draft);
let result = sm.transition(mailing_stateTransition::Pickup);
assert!(result.is_err());
}
#[test]
fn test_state_parsing() {
let state: mailing_stateState = "draft".parse().unwrap();
assert_eq!(state, mailing_stateState::Draft);
}
#[test]
fn test_available_transitions() {
let sm = mailing_stateStateMachine::new();
let available = sm.available_transitions();
assert!(!available.is_empty() || sm.current_state().is_final());
}
}