Enforce Order State Transitions with Enums

Model order status as an enum and allow only the transitions place, pay, ship, and cancel from valid states.

RustData Integrity

Code

snippet
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum OrderState {
    Draft,
    Placed,
    Paid,
    Shipped,
    Cancelled,
}

#[derive(Debug, PartialEq, Eq)]
enum TransitionError {
    InvalidTransition {
        from: OrderState,
        event: &'static str,
    },
}

struct Order {
    state: OrderState,
}

impl Order {
    fn new() -> Self {
        Self {
            state: OrderState::Draft,
        }
    }

    fn apply(&mut self, event: &'static str) -> Result<OrderState, TransitionError> {
        use OrderState::*;
        let next = match (self.state, event) {
            (Draft, "place") => Placed,
            (Placed, "pay") => Paid,
            (Paid, "ship") => Shipped,
            (Draft | Placed, "cancel") => Cancelled,
            (_, _) => {
                return Err(TransitionError::InvalidTransition {
                    from: self.state,
                    event,
                });
            }
        };
        self.state = next;
        Ok(next)
    }
}

fn main() {
    let mut order = Order::new();
    assert_eq!(
        order.apply("pay"),
        Err(TransitionError::InvalidTransition {
            from: OrderState::Draft,
            event: "pay",
        })
    );
    assert_eq!(order.apply("place"), Ok(OrderState::Placed));
    assert_eq!(order.apply("pay"), Ok(OrderState::Paid));
    assert_eq!(
        order.apply("cancel"),
        Err(TransitionError::InvalidTransition {
            from: OrderState::Paid,
            event: "cancel",
        })
    );
    assert_eq!(order.apply("ship"), Ok(OrderState::Shipped));
    assert_eq!(
        order.apply("ship"),
        Err(TransitionError::InvalidTransition {
            from: OrderState::Shipped,
            event: "ship",
        })
    );
    println!("final state: {:?}", order.state);
}