use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
pub fn derive_status_state_machine(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let enum_name = &input.ident;
eprintln!("[pleme-codegen] StatusStateMachine pattern applied to {} - saving ~110 lines", enum_name);
let expanded = quote! {
impl #enum_name {
pub fn can_transition_to(&self, new_status: &#enum_name) -> bool {
if std::mem::discriminant(self) == std::mem::discriminant(new_status) {
return true;
}
let from = format!("{:?}", self);
let to = format!("{:?}", new_status);
match (from.as_str(), to.as_str()) {
("Pending", "AwaitingPayment") | ("Pending", "PaymentProcessing") |
("Pending", "Paid") | ("Pending", "Failed") | ("Pending", "Cancelled") => true,
("AwaitingPayment", "PaymentProcessing") | ("AwaitingPayment", "Paid") |
("AwaitingPayment", "Failed") | ("AwaitingPayment", "Cancelled") |
("AwaitingPayment", "Expired") => true,
("PaymentProcessing", "Paid") | ("PaymentProcessing", "Failed") |
("PaymentProcessing", "Cancelled") | ("PaymentProcessing", "Authorized") => true,
("Authorized", "Captured") | ("Authorized", "Cancelled") | ("Authorized", "Expired") => true,
("Captured", "Processing") | ("Captured", "Refunded") => true,
("Paid", "Processing") | ("Paid", "Cancelled") | ("Paid", "Refunded") => true,
("Processing", "Fulfilled") | ("Processing", "PartiallyFulfilled") |
("Processing", "Cancelled") | ("Processing", "Failed") => true,
("PartiallyFulfilled", "Fulfilled") | ("PartiallyFulfilled", "Cancelled") => true,
("Fulfilled", "Shipped") | ("Fulfilled", "PartiallyShipped") => true,
("PartiallyShipped", "Shipped") => true,
("Shipped", "OutForDelivery") | ("Shipped", "Delivered") | ("Shipped", "Returned") => true,
("OutForDelivery", "Delivered") | ("OutForDelivery", "Returned") => true,
("Delivered", "Refunded") | ("Delivered", "PartiallyRefunded") |
("Delivered", "Disputed") | ("Delivered", "Returned") => true,
("PartiallyRefunded", "Refunded") | ("PartiallyRefunded", "Disputed") => true,
("Returned", "Refunded") => true,
("Active", "Inactive") | ("Active", "Suspended") | ("Active", "Deleted") => true,
("Inactive", "Active") | ("Inactive", "Deleted") => true,
("Suspended", "Active") | ("Suspended", "Deleted") => true,
_ => false
}
}
pub fn is_final_status(&self) -> bool {
let status_str = format!("{:?}", self);
matches!(
status_str.as_str(),
"Delivered" | "Cancelled" | "Refunded" | "Failed" |
"Expired" | "Disputed" | "Deleted" | "Returned"
)
}
pub fn can_be_cancelled(&self) -> bool {
if self.is_final_status() {
return false;
}
let status_str = format!("{:?}", self);
matches!(
status_str.as_str(),
"Pending" | "AwaitingPayment" | "PaymentProcessing" |
"Paid" | "Processing" | "Authorized"
)
}
pub fn can_be_refunded(&self) -> bool {
let status_str = format!("{:?}", self);
matches!(
status_str.as_str(),
"Paid" | "Captured" | "Processing" | "PartiallyFulfilled" | "Fulfilled" |
"Shipped" | "PartiallyShipped" | "OutForDelivery" | "Delivered" |
"PartiallyRefunded" | "Returned"
)
}
pub fn to_str(&self) -> &'static str {
let variant = format!("{:?}", self);
match variant.as_str() {
"Pending" => "pending",
"AwaitingPayment" => "awaiting_payment",
"PaymentProcessing" => "payment_processing",
"Paid" => "paid",
"Processing" => "processing",
"PartiallyFulfilled" => "partially_fulfilled",
"Fulfilled" => "fulfilled",
"Shipped" => "shipped",
"PartiallyShipped" => "partially_shipped",
"OutForDelivery" => "out_for_delivery",
"Delivered" => "delivered",
"Cancelled" => "cancelled",
"Refunded" => "refunded",
"PartiallyRefunded" => "partially_refunded",
"Disputed" => "disputed",
"Failed" => "failed",
"Expired" => "expired",
"Authorized" => "authorized",
"Captured" => "captured",
"Returned" => "returned",
"Active" => "active",
"Inactive" => "inactive",
"Suspended" => "suspended",
"Deleted" => "deleted",
_ => "unknown"
}
}
}
impl std::str::FromStr for #enum_name {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let error_msg = format!("Invalid {}: {}", stringify!(#enum_name), s);
match s {
"pending" => {
if let Ok(parsed) = s.parse::<Self>() {
return Ok(parsed);
}
}
_ => {}
}
Err(error_msg)
}
}
};
TokenStream::from(expanded)
}