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 chrono::{DateTime, Utc};
21use serde::{Deserialize, Serialize};
22
23/// A recorded state transition.
24///
25/// Captures the from/to states, the event that triggered the transition,
26/// and when it occurred.
27///
28/// # Examples
29///
30/// ```
31/// use ironflow_engine::fsm::Transition;
32///
33/// let t: Transition<String, String> = Transition {
34///     from: "Pending".to_string(),
35///     to: "Running".to_string(),
36///     event: "picked_up".to_string(),
37///     at: chrono::Utc::now(),
38/// };
39/// ```
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct Transition<S, E> {
42    /// State before the transition.
43    pub from: S,
44    /// State after the transition.
45    pub to: S,
46    /// Event that triggered the transition.
47    pub event: E,
48    /// When the transition occurred.
49    pub at: DateTime<Utc>,
50}
51
52/// Error returned when a transition is not allowed.
53///
54/// # Examples
55///
56/// ```
57/// use ironflow_engine::fsm::TransitionError;
58///
59/// let err: TransitionError<String, String> = TransitionError {
60///     from: "Completed".to_string(),
61///     event: "picked_up".to_string(),
62/// };
63/// assert!(err.to_string().contains("Completed"));
64/// ```
65#[derive(Debug, Clone)]
66pub struct TransitionError<S: std::fmt::Display, E: std::fmt::Display> {
67    /// Current state when the invalid transition was attempted.
68    pub from: S,
69    /// Event that was rejected.
70    pub event: E,
71}
72
73impl<S: std::fmt::Display, E: std::fmt::Display> std::fmt::Display for TransitionError<S, E> {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        write!(
76            f,
77            "invalid transition: event '{}' not allowed in state '{}'",
78            self.event, self.from
79        )
80    }
81}
82
83impl<S: std::fmt::Debug + std::fmt::Display, E: std::fmt::Debug + std::fmt::Display>
84    std::error::Error for TransitionError<S, E>
85{
86}