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.
Code
#[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);
}
Report a problem with this exampleOpens a pre-filled issue on GitHub.