Skip to main content

walletsuite_tx_compiler/
error.rs

1//! Error codes and the [`TxCompilerError`] type.
2
3use serde::{Deserialize, Serialize};
4use std::fmt;
5
6/// Discriminator for the class of error raised by the compiler.
7///
8/// The variants use stable string discriminators so downstream consumers
9/// can share audit receipts and error vocabulary across versions.
10#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
12#[non_exhaustive]
13pub enum TxCompilerErrorCode {
14    /// The caller passed an input that failed top-level shape checks.
15    InvalidPayload,
16    /// The chain identifier is not one this crate compiles.
17    UnsupportedChain,
18    /// The transaction type is not recognised.
19    UnsupportedTxType,
20    /// The fee mode is not valid for the target chain.
21    UnsupportedFeeMode,
22    /// An address failed chain-specific validation.
23    InvalidAddress,
24    /// A numeric amount (wei, SUN, gas unit) was malformed.
25    InvalidAmount,
26    /// A mode-required fee parameter was absent.
27    MissingFeeParams,
28    /// The Tron block-header reference is malformed or incomplete.
29    InvalidBlockHeader,
30    /// An EVM ERC-20 calldata payload was malformed.
31    InvalidCalldata,
32    /// A downstream compilation step failed unexpectedly.
33    CompilationFailed,
34}
35
36impl TxCompilerErrorCode {
37    /// Stable string representation used in audit records and error JSON.
38    #[must_use]
39    pub const fn as_str(self) -> &'static str {
40        match self {
41            Self::InvalidPayload => "INVALID_PAYLOAD",
42            Self::UnsupportedChain => "UNSUPPORTED_CHAIN",
43            Self::UnsupportedTxType => "UNSUPPORTED_TX_TYPE",
44            Self::UnsupportedFeeMode => "UNSUPPORTED_FEE_MODE",
45            Self::InvalidAddress => "INVALID_ADDRESS",
46            Self::InvalidAmount => "INVALID_AMOUNT",
47            Self::MissingFeeParams => "MISSING_FEE_PARAMS",
48            Self::InvalidBlockHeader => "INVALID_BLOCK_HEADER",
49            Self::InvalidCalldata => "INVALID_CALLDATA",
50            Self::CompilationFailed => "COMPILATION_FAILED",
51        }
52    }
53}
54
55impl fmt::Display for TxCompilerErrorCode {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.write_str(self.as_str())
58    }
59}
60
61/// Structured error returned by every public operation.
62///
63/// The error carries a machine-readable [`TxCompilerErrorCode`], a
64/// human-readable message, and an optional JSON object with debug context.
65///
66/// Only [`TxCompilerError::code`] is part of the stable API: downstream
67/// code should match on `code` for branching logic. The `message` text
68/// is human-readable and MAY change between versions for clarity.
69/// `details` is an unstable debug payload — do not pattern-match on
70/// its keys.
71#[derive(Debug, Clone, Serialize, Deserialize)]
72#[non_exhaustive]
73pub struct TxCompilerError {
74    /// Machine-readable error discriminator.
75    pub code: TxCompilerErrorCode,
76    /// Human-readable description of the failure. Not part of the
77    /// stable API — do not pattern-match on contents.
78    pub message: String,
79    /// Optional structured context useful for debugging. Unstable;
80    /// do not pattern-match on keys.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub details: Option<serde_json::Value>,
83}
84
85impl TxCompilerError {
86    /// Build an error without a `details` payload.
87    #[must_use]
88    pub fn new(code: TxCompilerErrorCode, message: impl Into<String>) -> Self {
89        Self {
90            code,
91            message: message.into(),
92            details: None,
93        }
94    }
95
96    /// Build an error with a structured `details` payload.
97    #[must_use]
98    pub fn with_details(
99        code: TxCompilerErrorCode,
100        message: impl Into<String>,
101        details: serde_json::Value,
102    ) -> Self {
103        Self {
104            code,
105            message: message.into(),
106            details: Some(details),
107        }
108    }
109}
110
111impl fmt::Display for TxCompilerError {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(f, "{}: {}", self.code, self.message)
114    }
115}
116
117impl std::error::Error for TxCompilerError {}