rust_x402/
error.rs

1//! Error types for the x402 library
2
3use thiserror::Error;
4
5#[cfg(feature = "actix-web")]
6use actix_web::{HttpResponse, ResponseError};
7
8/// Result type alias for x402 operations
9pub type Result<T> = std::result::Result<T, X402Error>;
10
11/// Main error type for x402 operations
12#[derive(Error, Debug)]
13pub enum X402Error {
14    /// JSON serialization/deserialization error
15    #[error("JSON error: {0}")]
16    Json(#[from] serde_json::Error),
17
18    /// HTTP client error
19    #[error("HTTP error: {0}")]
20    Http(#[from] reqwest::Error),
21
22    /// Base64 encoding/decoding error
23    #[error("Base64 error: {0}")]
24    Base64(#[from] base64::DecodeError),
25
26    /// Invalid payment payload
27    #[error("Invalid payment payload: {message}")]
28    InvalidPaymentPayload { message: String },
29
30    /// Invalid payment requirements
31    #[error("Invalid payment requirements: {message}")]
32    InvalidPaymentRequirements { message: String },
33
34    /// Payment verification failed
35    #[error("Payment verification failed: {reason}")]
36    PaymentVerificationFailed { reason: String },
37
38    /// Payment settlement failed
39    #[error("Payment settlement failed: {reason}")]
40    PaymentSettlementFailed { reason: String },
41
42    /// Facilitator communication error
43    #[error("Facilitator error: {message}")]
44    FacilitatorError { message: String },
45
46    /// Cryptographic error
47    #[error("Cryptographic error: {0}")]
48    Crypto(#[from] Box<dyn std::error::Error + Send + Sync>),
49
50    /// Invalid signature
51    #[error("Invalid signature: {message}")]
52    InvalidSignature { message: String },
53
54    /// Invalid authorization
55    #[error("Invalid authorization: {message}")]
56    InvalidAuthorization { message: String },
57
58    /// Network not supported
59    #[error("Network not supported: {network}")]
60    NetworkNotSupported { network: String },
61
62    /// Network error (connection, RPC, etc.)
63    #[error("Network error: {message}")]
64    NetworkError { message: String },
65
66    /// Invalid network configuration
67    #[error("Invalid network: {message}")]
68    InvalidNetwork { message: String },
69
70    /// Scheme not supported
71    #[error("Scheme not supported: {scheme}")]
72    SchemeNotSupported { scheme: String },
73
74    /// Insufficient funds
75    #[error("Insufficient funds")]
76    InsufficientFunds,
77
78    /// Authorization expired
79    #[error("Authorization expired")]
80    AuthorizationExpired,
81
82    /// Authorization not yet valid
83    #[error("Authorization not yet valid")]
84    AuthorizationNotYetValid,
85
86    /// Invalid amount
87    #[error("Invalid amount: expected {expected}, got {got}")]
88    InvalidAmount { expected: String, got: String },
89
90    /// Recipient mismatch
91    #[error("Recipient mismatch: expected {expected}, got {got}")]
92    RecipientMismatch { expected: String, got: String },
93
94    /// Unexpected error
95    #[error("Unexpected error: {message}")]
96    Unexpected { message: String },
97
98    /// Configuration error
99    #[error("Configuration error: {message}")]
100    Config { message: String },
101
102    /// Timeout error
103    #[error("Request timeout")]
104    Timeout,
105
106    /// IO error
107    #[error("IO error: {0}")]
108    Io(#[from] std::io::Error),
109
110    /// Parse integer error
111    #[error("Parse integer error: {0}")]
112    ParseInt(#[from] std::num::ParseIntError),
113
114    /// From hex error
115    #[error("From hex error: {0}")]
116    FromHex(#[from] rustc_hex::FromHexError),
117
118    /// From str radix error
119    #[error("From str radix error: {0}")]
120    FromStrRadix(#[from] ethereum_types::FromStrRadixErr),
121
122    /// Rustls error
123    #[cfg(feature = "http3")]
124    #[error("Rustls error: {0}")]
125    Rustls(#[from] rustls::Error),
126
127    /// Certificate generation error
128    #[cfg(feature = "http3")]
129    #[error("Certificate generation error: {0}")]
130    CertGen(#[from] rcgen::Error),
131
132    /// Address parse error
133    #[error("Address parse error: {0}")]
134    AddrParse(#[from] std::net::AddrParseError),
135
136    /// Quinn error
137    #[cfg(feature = "http3")]
138    #[error("Quinn connection error: {0}")]
139    Quinn(#[from] quinn::ConnectionError),
140
141    /// H3 error
142    #[cfg(feature = "http3")]
143    #[error("H3 connection error: {0}")]
144    H3(#[from] h3::error::ConnectionError),
145}
146
147impl X402Error {
148    /// Create an invalid payment payload error
149    pub fn invalid_payment_payload(message: impl Into<String>) -> Self {
150        Self::InvalidPaymentPayload {
151            message: message.into(),
152        }
153    }
154
155    /// Create an invalid payment requirements error
156    pub fn invalid_payment_requirements(message: impl Into<String>) -> Self {
157        Self::InvalidPaymentRequirements {
158            message: message.into(),
159        }
160    }
161
162    /// Create a payment verification failed error
163    pub fn payment_verification_failed(reason: impl Into<String>) -> Self {
164        Self::PaymentVerificationFailed {
165            reason: reason.into(),
166        }
167    }
168
169    /// Create a payment settlement failed error
170    pub fn payment_settlement_failed(reason: impl Into<String>) -> Self {
171        Self::PaymentSettlementFailed {
172            reason: reason.into(),
173        }
174    }
175
176    /// Create a facilitator error
177    pub fn facilitator_error(message: impl Into<String>) -> Self {
178        Self::FacilitatorError {
179            message: message.into(),
180        }
181    }
182
183    /// Create an invalid signature error
184    pub fn invalid_signature(message: impl Into<String>) -> Self {
185        Self::InvalidSignature {
186            message: message.into(),
187        }
188    }
189
190    /// Create an invalid authorization error
191    pub fn invalid_authorization(message: impl Into<String>) -> Self {
192        Self::InvalidAuthorization {
193            message: message.into(),
194        }
195    }
196
197    /// Create a network error
198    pub fn network_error(message: impl Into<String>) -> Self {
199        Self::NetworkError {
200            message: message.into(),
201        }
202    }
203
204    /// Create an invalid network error
205    pub fn invalid_network(message: impl Into<String>) -> Self {
206        Self::InvalidNetwork {
207            message: message.into(),
208        }
209    }
210
211    /// Create an unexpected error
212    pub fn unexpected(message: impl Into<String>) -> Self {
213        Self::Unexpected {
214            message: message.into(),
215        }
216    }
217
218    /// Create a configuration error
219    pub fn config(message: impl Into<String>) -> Self {
220        Self::Config {
221            message: message.into(),
222        }
223    }
224
225    /// Get HTTP status code for this error
226    pub fn status_code(&self) -> u16 {
227        match self {
228            Self::InvalidPaymentPayload { .. } => 400,
229            Self::InvalidPaymentRequirements { .. } => 400,
230            Self::PaymentVerificationFailed { .. } => 402,
231            Self::PaymentSettlementFailed { .. } => 402,
232            Self::FacilitatorError { .. } => 502,
233            Self::InvalidSignature { .. } => 400,
234            Self::InvalidAuthorization { .. } => 401,
235            Self::NetworkNotSupported { .. } => 400,
236            Self::NetworkError { .. } => 502,
237            Self::InvalidNetwork { .. } => 400,
238            Self::SchemeNotSupported { .. } => 400,
239            Self::InsufficientFunds => 402,
240            Self::AuthorizationExpired => 401,
241            Self::AuthorizationNotYetValid => 401,
242            Self::InvalidAmount { .. } => 400,
243            Self::RecipientMismatch { .. } => 400,
244            Self::Unexpected { .. } => 500,
245            Self::Config { .. } => 500,
246            Self::Timeout => 408,
247            Self::Json(_) => 400,
248            Self::Http(_) => 502,
249            Self::Base64(_) => 400,
250            Self::Crypto(_) => 500,
251            Self::Io(_) => 500,
252            Self::ParseInt(_) => 400,
253            Self::FromHex(_) => 400,
254            Self::FromStrRadix(_) => 400,
255            #[cfg(feature = "http3")]
256            Self::Rustls(_) => 500,
257            #[cfg(feature = "http3")]
258            Self::CertGen(_) => 500,
259            Self::AddrParse(_) => 400,
260            #[cfg(feature = "http3")]
261            Self::Quinn(_) => 502,
262            #[cfg(feature = "http3")]
263            Self::H3(_) => 502,
264        }
265    }
266
267    /// Get error type string
268    pub fn error_type(&self) -> &'static str {
269        match self {
270            Self::InvalidPaymentPayload { .. } => "invalid_payment_payload",
271            Self::InvalidPaymentRequirements { .. } => "invalid_payment_requirements",
272            Self::PaymentVerificationFailed { .. } => "payment_verification_failed",
273            Self::PaymentSettlementFailed { .. } => "payment_settlement_failed",
274            Self::FacilitatorError { .. } => "facilitator_error",
275            Self::InvalidSignature { .. } => "invalid_signature",
276            Self::InvalidAuthorization { .. } => "invalid_authorization",
277            Self::NetworkNotSupported { .. } => "network_not_supported",
278            Self::NetworkError { .. } => "network_error",
279            Self::InvalidNetwork { .. } => "invalid_network",
280            Self::SchemeNotSupported { .. } => "scheme_not_supported",
281            Self::InsufficientFunds => "insufficient_funds",
282            Self::AuthorizationExpired => "authorization_expired",
283            Self::AuthorizationNotYetValid => "authorization_not_yet_valid",
284            Self::InvalidAmount { .. } => "invalid_amount",
285            Self::RecipientMismatch { .. } => "recipient_mismatch",
286            Self::Unexpected { .. } => "unexpected_error",
287            Self::Config { .. } => "configuration_error",
288            Self::Timeout => "timeout",
289            Self::Json(_) => "json_error",
290            Self::Http(_) => "http_error",
291            Self::Base64(_) => "base64_error",
292            Self::Crypto(_) => "crypto_error",
293            Self::Io(_) => "io_error",
294            Self::ParseInt(_) => "parse_int_error",
295            Self::FromHex(_) => "from_hex_error",
296            Self::FromStrRadix(_) => "from_str_radix_error",
297            #[cfg(feature = "http3")]
298            Self::Rustls(_) => "rustls_error",
299            #[cfg(feature = "http3")]
300            Self::CertGen(_) => "cert_gen_error",
301            Self::AddrParse(_) => "addr_parse_error",
302            #[cfg(feature = "http3")]
303            Self::Quinn(_) => "quinn_error",
304            #[cfg(feature = "http3")]
305            Self::H3(_) => "h3_error",
306        }
307    }
308}
309
310/// Unified error response structure
311#[derive(Debug, Clone, serde::Serialize)]
312pub struct ErrorResponse {
313    /// Error message
314    pub error: String,
315    /// Error type
316    #[serde(rename = "type")]
317    pub error_type: String,
318    /// HTTP status code
319    pub status_code: u16,
320    /// Protocol version
321    #[serde(rename = "x402Version")]
322    pub x402_version: u32,
323    /// Additional error details
324    #[serde(skip_serializing_if = "Option::is_none")]
325    pub details: Option<serde_json::Value>,
326}
327
328impl ErrorResponse {
329    /// Create a new error response from X402Error
330    pub fn from_x402_error(error: &X402Error) -> Self {
331        Self {
332            error: error.to_string(),
333            error_type: error.error_type().to_string(),
334            status_code: error.status_code(),
335            x402_version: 1,
336            details: None,
337        }
338    }
339
340    /// Create a new error response with custom message
341    pub fn new(error: impl Into<String>, error_type: impl Into<String>, status_code: u16) -> Self {
342        Self {
343            error: error.into(),
344            error_type: error_type.into(),
345            status_code,
346            x402_version: 1,
347            details: None,
348        }
349    }
350
351    /// Add error details
352    pub fn with_details(mut self, details: serde_json::Value) -> Self {
353        self.details = Some(details);
354        self
355    }
356}
357
358impl From<&X402Error> for ErrorResponse {
359    fn from(error: &X402Error) -> Self {
360        Self::from_x402_error(error)
361    }
362}
363
364#[cfg(feature = "actix-web")]
365impl ResponseError for X402Error {
366    fn error_response(&self) -> HttpResponse {
367        let error_response = ErrorResponse::from_x402_error(self);
368        let status_code = actix_web::http::StatusCode::from_u16(self.status_code())
369            .unwrap_or(actix_web::http::StatusCode::INTERNAL_SERVER_ERROR);
370
371        HttpResponse::build(status_code).json(error_response)
372    }
373}