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}