Skip to main content

order/
order.rs

1//! A comprehensive example of using nanomachine to model an e-commerce order
2//! FSM: Created → Paid → Shipped → Delivered, with the option to Cancel at
3//! various points.
4
5use nanomachine::Machine;
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8enum OrderState {
9    Created,
10    Paid,
11    Shipped,
12    Delivered,
13    Cancelled,
14}
15
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17enum OrderEvent {
18    Pay,     // With payload: amount in cents.
19    Ship,    // With payload: tracking number.
20    Deliver, // No payload.
21    Cancel,  // With payload: reason string.
22}
23
24fn main() {
25    // Create the machine in the Created state.
26    let mut nano = Machine::new(OrderState::Created);
27
28    // Define transitions one-by-one.
29    nano.when(OrderEvent::Pay, OrderState::Created, OrderState::Paid);
30    nano.when(OrderEvent::Ship, OrderState::Paid, OrderState::Shipped);
31    nano.when(OrderEvent::Deliver, OrderState::Shipped, OrderState::Delivered);
32
33    // Define Cancel for both Created and Paid using when_iter.
34    nano.when_iter(
35        OrderEvent::Cancel,
36        [
37            (OrderState::Created, OrderState::Cancelled),
38            (OrderState::Paid, OrderState::Cancelled),
39        ],
40    );
41
42    // ─── State-specific hooks ───────────────────────────────────────────────
43    // On Paid: log payment amount.
44    nano.on_enter_with(OrderState::Paid, |evt, amount: &u32| {
45        println!("✔ Payment {:?}: {}¢ received", evt, amount);
46    });
47
48    // On Shipped: log tracking code.
49    nano.on_enter_with(OrderState::Shipped, |_evt, tracking: &String| {
50        println!("🚚 Order shipped! Tracking code: {}", tracking);
51    });
52
53    // On Delivered: confirmation.
54    nano.on_enter_with(OrderState::Delivered, |_evt, _: &()| {
55        println!("📦 Order delivered. Thank you!");
56    });
57
58    // On Cancelled: show cancellation reason.
59    nano.on_enter_with(OrderState::Cancelled, |_evt, reason: &String| {
60        println!("❌ Order cancelled: {}", reason);
61    });
62
63    // ─── Global hooks ───────────────────────────────────────────────────────
64    // Fires on *every* transition (no payload).
65    nano.on_transition(|evt| {
66        println!("🔄 Transition via event: {:?}", evt);
67    });
68
69    // Fires on *every* transition *with* a u32 payload (e.g. payments).
70    nano.on_transition_with(|evt, amount: &u32| {
71        println!("🌐 Global saw {}¢ payment via {:?}", amount, evt);
72    });
73
74    // Fires on *every* transition *with* a String payload
75    // (e.g. tracking codes or cancellation reasons).
76    nano.on_transition_with(|evt, text: &String| {
77        println!("🌐 Global saw string payload {:?}: {}", evt, text);
78    });
79    // Inspect machine metadata:
80    println!("All states: {:?}", nano.states().cloned().collect::<Vec<_>>());
81    println!("All events: {:?}", nano.events().cloned().collect::<Vec<_>>());
82    println!(
83        "Triggerable now ({:?}): {:?}",
84        nano.state(),
85        nano.triggerable_events().cloned().collect::<Vec<_>>()
86    );
87    println!();
88
89    // Simulate a successful order:
90    //    Pay with payload.
91    nano.trigger_with(&OrderEvent::Pay, &2_500u32)
92        .expect("Should transition to Paid");
93    //    Ship with payload.
94    nano.trigger_with(&OrderEvent::Ship, &"TRACK1234".to_string())
95        .expect("Should transition to Shipped");
96    //    Deliver without payload.
97    nano.trigger(&OrderEvent::Deliver).expect("Should transition to Delivered");
98    println!("\nFinal state: {:?}\n", nano.state());
99
100    // Attempt an invalid transition (Pay after Delivered).
101    match nano.trigger(&OrderEvent::Pay) {
102        Ok(_) => println!("Unexpectedly succeeded!"),
103        Err(e) => println!("Error trying to pay in {:?}: {}", nano.state(), e),
104    }
105
106    // Create a fresh order and cancel it early.
107    let mut nano = Machine::new(OrderState::Created);
108    nano.when_iter(
109        OrderEvent::Cancel,
110        [
111            (OrderState::Created, OrderState::Cancelled),
112            (OrderState::Paid, OrderState::Cancelled),
113        ],
114    );
115    // Re-attach only the Cancel callback.
116    nano.on_enter_with(OrderState::Cancelled, |_evt, reason: &String| {
117        println!("Order #2 cancelled reason: {}", reason);
118    });
119    // Cancel with a payload.
120    nano.trigger_with(
121        &OrderEvent::Cancel,
122        &"Customer changed mind".to_string(),
123    )
124    .unwrap();
125
126    println!("Order #2 final: {:?}", nano.state());
127}