use serde::{Deserialize, Serialize}; use strum_macros::Display; /// Transaction status #[derive(Clone, Eq, PartialEq, Debug, Display, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Status { /// Pending status /// /// A transaction has been created. At this status the transaction can be /// Cancelled. /// From Processing it can be moved to Settled or Failed. Pending, /// Processing /// /// The transaction leaves the Pending status and it cannot longer be /// Cancelled by an external event. Processing, /// Cancelled. /// /// The transaction has been Cancelled, the funds that were burned are /// released and can be used again. Cancelled, /// Settled /// /// The transaction is settled, the inputs used to create the transaction /// are forever burned. Settled, /// Failed /// /// The transaction has failed, the inputs used to create the transaction /// are released and can be used again. Failed, } impl Default for Status { fn default() -> Self { Self::Pending } } #[derive(thiserror::Error, Debug)] pub enum Error { #[error("Invalid status: {0}")] InvalidStatus(u32), } impl TryFrom for Status { type Error = Error; fn try_from(value: u32) -> Result { match value { 0 => Ok(Self::Pending), 10 => Ok(Self::Processing), 20 => Ok(Self::Cancelled), 30 => Ok(Self::Settled), 40 => Ok(Self::Failed), _ => Err(Error::InvalidStatus(value)), } } } impl From for u32 { fn from(value: Status) -> Self { (&value).into() } } impl From<&Status> for u32 { fn from(value: &Status) -> Self { match value { Status::Pending => 0, Status::Processing => 10, Status::Cancelled => 20, Status::Settled => 30, Status::Failed => 40, } } } impl Status { /// Checks if the current status can transition to the new state. pub fn can_transition_to(&self, new_status: &Status) -> bool { if self == new_status { false } else { match self { Self::Pending => true, Self::Processing => { matches!(new_status, Self::Settled | Self::Failed) } _ => false, } } } /// Checks if the current status is a rollback operation. /// /// In a rollback operation any previously spent payments are released by the storage layer. pub fn is_rollback(&self) -> bool { matches!(self, Self::Cancelled | Self::Failed) } /// Checks if the transaction status is finalized pub fn is_finalized(&self) -> bool { matches!(self, Self::Cancelled | Self::Settled | Self::Failed) } } #[cfg(test)] mod test { use super::*; #[test] fn pending() { let status = Status::Pending; assert!(!status.can_transition_to(&Status::Pending)); assert!(status.can_transition_to(&Status::Processing)); assert!(status.can_transition_to(&Status::Cancelled)); assert!(status.can_transition_to(&Status::Settled)); assert!(status.can_transition_to(&Status::Failed)); assert!(!status.is_finalized()); assert!(!status.is_rollback()); } #[test] fn processing() { let status = Status::Processing; assert!(!status.can_transition_to(&Status::Pending)); assert!(!status.can_transition_to(&Status::Processing)); assert!(!status.can_transition_to(&Status::Cancelled)); assert!(status.can_transition_to(&Status::Settled)); assert!(status.can_transition_to(&Status::Failed)); assert!(!status.is_finalized()); assert!(!status.is_rollback()); } #[test] fn cancelled() { let status = Status::Cancelled; assert!(!status.can_transition_to(&Status::Pending)); assert!(!status.can_transition_to(&Status::Processing)); assert!(!status.can_transition_to(&Status::Cancelled)); assert!(!status.can_transition_to(&Status::Settled)); assert!(!status.can_transition_to(&Status::Failed)); assert!(status.is_finalized()); assert!(status.is_rollback()); } #[test] fn settled() { let status = Status::Settled; assert!(!status.can_transition_to(&Status::Pending)); assert!(!status.can_transition_to(&Status::Processing)); assert!(!status.can_transition_to(&Status::Cancelled)); assert!(!status.can_transition_to(&Status::Settled)); assert!(!status.can_transition_to(&Status::Failed)); assert!(status.is_finalized()); assert!(!status.is_rollback()); } #[test] fn failed() { let status = Status::Failed; assert!(!status.can_transition_to(&Status::Pending)); assert!(!status.can_transition_to(&Status::Processing)); assert!(!status.can_transition_to(&Status::Cancelled)); assert!(!status.can_transition_to(&Status::Settled)); assert!(!status.can_transition_to(&Status::Failed)); assert!(status.is_finalized()); assert!(status.is_rollback()); } }