error.rs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. //! Error types for the async ledger layer.
  2. //!
  3. //! [`LedgerError`] unifies errors from the pure core (validation, selection)
  4. //! and from storage, so callers get a single error type from every API.
  5. use kuatia_core::{
  6. AccountId, AssetId, BookId, EnvelopeId, OverflowError, PostingId, SelectionError,
  7. ValidationError,
  8. };
  9. use kuatia_storage::error::StoreError;
  10. /// Unified error type for the async ledger API.
  11. #[derive(Debug)]
  12. pub enum LedgerError {
  13. /// A transfer invariant was violated.
  14. Validation(ValidationError),
  15. /// Storage operation failed.
  16. Store(StoreError),
  17. /// Posting selection failed (e.g. insufficient funds).
  18. Selection(SelectionError),
  19. /// The referenced transfer does not exist.
  20. TransferNotFound(EnvelopeId),
  21. /// The posting cannot be reversed (e.g. already consumed).
  22. PostingNotReversible(PostingId),
  23. /// The referenced account does not exist.
  24. AccountNotFound(AccountId),
  25. /// Cannot close an account that still has active postings.
  26. AccountNotEmpty(AccountId),
  27. /// The account is already closed.
  28. AccountAlreadyClosed(AccountId),
  29. /// A transfer named a book that does not exist.
  30. BookNotFound(BookId),
  31. /// The referenced inflight transaction does not exist (no authorize record).
  32. InflightNotFound(EnvelopeId),
  33. /// The referenced transfer is not an inflight authorize, or its metadata is
  34. /// malformed.
  35. NotInflightTransaction(EnvelopeId),
  36. /// The destination already has an open inflight hold; only one is allowed at
  37. /// a time per account.
  38. InflightAlreadyOpen(AccountId),
  39. /// The inflight transaction has no leg matching this destination and asset.
  40. InflightLegNotFound {
  41. /// The destination account with no matching leg.
  42. destination: AccountId,
  43. /// The asset with no matching leg.
  44. asset: AssetId,
  45. },
  46. /// An inflight movement must move between two distinct accounts.
  47. InflightSelfMovement(AccountId),
  48. /// Monetary arithmetic overflow.
  49. Overflow,
  50. /// A saga step failed and its compensation also failed.
  51. CompensationFailed {
  52. /// The error that triggered compensation.
  53. original: Box<LedgerError>,
  54. /// The error that occurred during compensation.
  55. compensation: Box<LedgerError>,
  56. },
  57. }
  58. impl std::fmt::Display for LedgerError {
  59. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  60. match self {
  61. Self::Validation(e) => write!(f, "validation: {e}"),
  62. Self::Store(e) => write!(f, "store: {e}"),
  63. Self::Selection(e) => write!(f, "selection: {e}"),
  64. Self::TransferNotFound(id) => write!(f, "transfer not found: {id:?}"),
  65. Self::PostingNotReversible(id) => write!(f, "posting not reversible: {id:?}"),
  66. Self::AccountNotFound(id) => write!(f, "account not found: {id:?}"),
  67. Self::AccountNotEmpty(id) => write!(f, "account not empty: {id:?}"),
  68. Self::AccountAlreadyClosed(id) => write!(f, "account already closed: {id:?}"),
  69. Self::BookNotFound(id) => write!(f, "book not found: {id:?}"),
  70. Self::InflightNotFound(id) => write!(f, "inflight transaction not found: {id:?}"),
  71. Self::NotInflightTransaction(id) => {
  72. write!(f, "not an inflight authorize transaction: {id:?}")
  73. }
  74. Self::InflightAlreadyOpen(id) => {
  75. write!(f, "account already has an open inflight hold: {id:?}")
  76. }
  77. Self::InflightLegNotFound { destination, asset } => write!(
  78. f,
  79. "inflight leg not found for destination {destination:?} asset {asset:?}"
  80. ),
  81. Self::InflightSelfMovement(id) => {
  82. write!(f, "inflight movement must have distinct from/to: {id:?}")
  83. }
  84. Self::Overflow => write!(f, "monetary amount overflow"),
  85. Self::CompensationFailed {
  86. original,
  87. compensation,
  88. } => write!(
  89. f,
  90. "compensation failed: original={original}, compensation={compensation}"
  91. ),
  92. }
  93. }
  94. }
  95. impl std::error::Error for LedgerError {
  96. fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
  97. match self {
  98. Self::Validation(e) => Some(e),
  99. Self::Store(e) => Some(e),
  100. Self::Selection(e) => Some(e),
  101. Self::Overflow => Some(&OverflowError),
  102. Self::CompensationFailed { original, .. } => Some(original.as_ref()),
  103. _ => None,
  104. }
  105. }
  106. }
  107. impl From<ValidationError> for LedgerError {
  108. fn from(e: ValidationError) -> Self {
  109. LedgerError::Validation(e)
  110. }
  111. }
  112. impl From<StoreError> for LedgerError {
  113. fn from(e: StoreError) -> Self {
  114. LedgerError::Store(e)
  115. }
  116. }
  117. impl From<SelectionError> for LedgerError {
  118. fn from(e: SelectionError) -> Self {
  119. LedgerError::Selection(e)
  120. }
  121. }
  122. impl From<OverflowError> for LedgerError {
  123. fn from(_: OverflowError) -> Self {
  124. LedgerError::Overflow
  125. }
  126. }