Skip to main content

ironflow_engine/fsm/
mod.rs

1//! Finite State Machines for workflow run and step lifecycles.
2//!
3//! Instead of ad-hoc `can_transition_to()` checks scattered across the codebase,
4//! these FSMs provide typed events, explicit transition tables, and a history
5//! of transitions for observability.
6//!
7//! # Architecture
8//!
9//! - [`RunFsm`] — Manages the lifecycle of a [`Run`](ironflow_store::entities::Run).
10//! - [`StepFsm`] — Manages the lifecycle of a [`Step`](ironflow_store::entities::Step).
11//! - [`RunEvent`] / [`StepEvent`] -- Typed events that trigger transitions.
12//! - [`Transition`] — A recorded state change with event and timestamp.
13
14mod run_fsm;
15mod step_fsm;
16
17pub use run_fsm::{RunEvent, RunFsm};
18pub use step_fsm::{StepEvent, StepFsm};
19
20use std::fmt;
21
22use chrono::{DateTime, Utc};
23use serde::{Deserialize, Serialize};
24
25/// A recorded state transition.
26///
27/// Captures the from/to states, the event that triggered the transition,
28/// and when it occurred.
29///
30/// # Examples
31///
32/// ```
33/// use ironflow_engine::fsm::Transition;
34///
35/// let t: Transition<String, String> = Transition {
36///     from: "Pending".to_string(),
37///     to: "Running".to_string(),
38///     event: "picked_up".to_string(),
39///     at: chrono::Utc::now(),
40/// };
41/// ```
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Transition<S, E> {
44    /// State before the transition.
45    pub from: S,
46    /// State after the transition.
47    pub to: S,
48    /// Event that triggered the transition.
49    pub event: E,
50    /// When the transition occurred.
51    pub at: DateTime<Utc>,
52}
53
54/// Error returned when a transition is not allowed.
55///
56/// # Examples
57///
58/// ```
59/// use ironflow_engine::fsm::TransitionError;
60///
61/// let err: TransitionError<String, String> = TransitionError {
62///     from: "Completed".to_string(),
63///     event: "picked_up".to_string(),
64/// };
65/// assert!(err.to_string().contains("Completed"));
66/// ```
67#[derive(Debug, Clone)]
68pub struct TransitionError<S: fmt::Display, E: fmt::Display> {
69    /// Current state when the invalid transition was attempted.
70    pub from: S,
71    /// Event that was rejected.
72    pub event: E,
73}
74
75impl<S: fmt::Display, E: fmt::Display> fmt::Display for TransitionError<S, E> {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        write!(
78            f,
79            "invalid transition: event '{}' not allowed in state '{}'",
80            self.event, self.from
81        )
82    }
83}
84
85impl<S: fmt::Debug + fmt::Display, E: fmt::Debug + fmt::Display> std::error::Error
86    for TransitionError<S, E>
87{
88}