use crate::MaxLengthString; use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// Status type /// /// The statuses are bare strings of at most 10 characters pub type Status = MaxLengthString<10>; #[derive(Debug, Serialize, Deserialize)] /// Status manager /// /// This struct manages the status of the payments and their life cycle. pub struct StatusManager { /// List of all statuses statuses: Vec, /// List of all spendable statuses spendable: Vec, /// The default spendable status default_spendable: Status, /// List of all reverted statuses reverted: Vec, transition: HashMap>, } #[derive(Debug, Clone, Copy)] /// Internal status type /// /// There could be many statuses, but they are translated into three types: pub enum InternalStatus { /// The payment is spendable, because it is final and it has been successfully processed Spendable, /// The payment is reverted, because it has been cancelled or failed Reverted, /// The payment is not created but still not final, it may be visible or not on the account's /// balance, but it is not spendable Pending, } /// Status error object #[derive(Debug, Serialize, thiserror::Error)] pub enum Error { #[error("Unknown status: {0}")] UnknownStatus(String), #[error("Invalid transition from {0} to {1}")] InvalidTransition(Status, Status), } impl StatusManager { /// Creates a new status from a given string pub fn new_status(&self, status_name: &str) -> Result { self.statuses .iter() .find(|status| *status == status_name) .cloned() .ok_or(Error::UnknownStatus(status_name.to_owned())) } /// Translated the status into an internal status type pub fn internal_type(&self, status: &Status) -> InternalStatus { if self.is_spendable(status) { InternalStatus::Spendable } else if self.is_reverted(status) { InternalStatus::Reverted } else { InternalStatus::Pending } } /// Whether the status is spendable or not pub fn spendables(&self) -> &[Status] { &self.spendable } /// Whether the status is reverted or not pub fn default_spendable(&self) -> Status { self.default_spendable.clone() } /// Spendable statuses are the final state of a transaction that has finished as successfully /// therefore it is spendable. pub fn is_spendable(&self, status: &Status) -> bool { self.spendable.contains(status) } /// Reverted transactions are the final state of a transaction that has failed or cancelled and /// they inputs payments are reverted and spendedable. pub fn is_reverted(&self, status: &Status) -> bool { self.reverted.contains(status) } /// The transaction is final and cannot longer be updated pub fn is_final(&self, status: &Status) -> bool { self.is_spendable(status) || self.is_reverted(status) } /// Checks if the status transition is allowed pub fn is_valid_transition(&self, from: &Status, to: &Status) -> Result<(), Error> { if self.transition.get(from).map_or(false, |v| v.contains(to)) { Ok(()) } else { Err(Error::InvalidTransition(from.clone(), to.clone())) } } } impl Default for StatusManager { fn default() -> Self { let pending: Status = "pending".into(); let processing: Status = "processing".into(); let cancelled: Status = "cancelled".into(); let settled: Status = "settled".into(); let failed: Status = "failed".into(); Self { statuses: vec![ pending.clone(), processing.clone(), cancelled.clone(), settled.clone(), failed.clone(), ], default_spendable: settled.clone(), spendable: vec![settled.clone()], reverted: vec![cancelled.clone(), failed.clone()], transition: { let mut map = HashMap::new(); map.insert( pending.clone(), vec![processing.clone(), settled.clone(), cancelled.clone()], ); map.insert(processing.clone(), vec![settled, failed]); map }, } } }