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
123impl X402Error {
124    /// Create an invalid payment payload error
125    pub fn invalid_payment_payload(message: impl Into<String>) -> Self {
126        Self::InvalidPaymentPayload {
127            message: message.into(),
128        }
129    }
130
131    /// Create an invalid payment requirements error
132    pub fn invalid_payment_requirements(message: impl Into<String>) -> Self {
133        Self::InvalidPaymentRequirements {
134            message: message.into(),
135        }
136    }
137
138    /// Create a payment verification failed error
139    pub fn payment_verification_failed(reason: impl Into<String>) -> Self {
140        Self::PaymentVerificationFailed {
141            reason: reason.into(),
142        }
143    }
144
145    /// Create a payment settlement failed error
146    pub fn payment_settlement_failed(reason: impl Into<String>) -> Self {
147        Self::PaymentSettlementFailed {
148            reason: reason.into(),
149        }
150    }
151
152    /// Create a facilitator error
153    pub fn facilitator_error(message: impl Into<String>) -> Self {
154        Self::FacilitatorError {
155            message: message.into(),
156        }
157    }
158
159    /// Create an invalid signature error
160    pub fn invalid_signature(message: impl Into<String>) -> Self {
161        Self::InvalidSignature {
162            message: message.into(),
163        }
164    }
165
166    /// Create an invalid authorization error
167    pub fn invalid_authorization(message: impl Into<String>) -> Self {
168        Self::InvalidAuthorization {
169            message: message.into(),
170        }
171    }
172
173    /// Create a network error
174    pub fn network_error(message: impl Into<String>) -> Self {
175        Self::NetworkError {
176            message: message.into(),
177        }
178    }
179
180    /// Create an invalid network error
181    pub fn invalid_network(message: impl Into<String>) -> Self {
182        Self::InvalidNetwork {
183            message: message.into(),
184        }
185    }
186
187    /// Create an unexpected error
188    pub fn unexpected(message: impl Into<String>) -> Self {
189        Self::Unexpected {
190            message: message.into(),
191        }
192    }
193
194    /// Create a configuration error
195    pub fn config(message: impl Into<String>) -> Self {
196        Self::Config {
197            message: message.into(),
198        }
199    }
200
201    /// Get HTTP status code for this error
202    pub fn status_code(&self) -> u16 {
203        match self {
204            Self::InvalidPaymentPayload { .. } => 400,
205            Self::InvalidPaymentRequirements { .. } => 400,
206            Self::PaymentVerificationFailed { .. } => 402,
207            Self::PaymentSettlementFailed { .. } => 402,
208            Self::FacilitatorError { .. } => 502,
209            Self::InvalidSignature { .. } => 400,
210            Self::InvalidAuthorization { .. } => 401,
211            Self::NetworkNotSupported { .. } => 400,
212            Self::NetworkError { .. } => 502,
213            Self::InvalidNetwork { .. } => 400,
214            Self::SchemeNotSupported { .. } => 400,
215            Self::InsufficientFunds => 402,
216            Self::AuthorizationExpired => 401,
217            Self::AuthorizationNotYetValid => 401,
218            Self::InvalidAmount { .. } => 400,
219            Self::RecipientMismatch { .. } => 400,
220            Self::Unexpected { .. } => 500,
221            Self::Config { .. } => 500,
222            Self::Timeout => 408,
223            Self::Json(_) => 400,
224            Self::Http(_) => 502,
225            Self::Base64(_) => 400,
226            Self::Crypto(_) => 500,
227            Self::Io(_) => 500,
228            Self::ParseInt(_) => 400,
229            Self::FromHex(_) => 400,
230            Self::FromStrRadix(_) => 400,
231        }
232    }
233
234    /// Get error type string
235    pub fn error_type(&self) -> &'static str {
236        match self {
237            Self::InvalidPaymentPayload { .. } => "invalid_payment_payload",
238            Self::InvalidPaymentRequirements { .. } => "invalid_payment_requirements",
239            Self::PaymentVerificationFailed { .. } => "payment_verification_failed",
240            Self::PaymentSettlementFailed { .. } => "payment_settlement_failed",
241            Self::FacilitatorError { .. } => "facilitator_error",
242            Self::InvalidSignature { .. } => "invalid_signature",
243            Self::InvalidAuthorization { .. } => "invalid_authorization",
244            Self::NetworkNotSupported { .. } => "network_not_supported",
245            Self::NetworkError { .. } => "network_error",
246            Self::InvalidNetwork { .. } => "invalid_network",
247            Self::SchemeNotSupported { .. } => "scheme_not_supported",
248            Self::InsufficientFunds => "insufficient_funds",
249            Self::AuthorizationExpired => "authorization_expired",
250            Self::AuthorizationNotYetValid => "authorization_not_yet_valid",
251            Self::InvalidAmount { .. } => "invalid_amount",
252            Self::RecipientMismatch { .. } => "recipient_mismatch",
253            Self::Unexpected { .. } => "unexpected_error",
254            Self::Config { .. } => "configuration_error",
255            Self::Timeout => "timeout",
256            Self::Json(_) => "json_error",
257            Self::Http(_) => "http_error",
258            Self::Base64(_) => "base64_error",
259            Self::Crypto(_) => "crypto_error",
260            Self::Io(_) => "io_error",
261            Self::ParseInt(_) => "parse_int_error",
262            Self::FromHex(_) => "from_hex_error",
263            Self::FromStrRadix(_) => "from_str_radix_error",
264        }
265    }
266}
267
268/// Unified error response structure
269#[derive(Debug, Clone, serde::Serialize)]
270pub struct ErrorResponse {
271    /// Error message
272    pub error: String,
273    /// Error type
274    #[serde(rename = "type")]
275    pub error_type: String,
276    /// HTTP status code
277    pub status_code: u16,
278    /// Protocol version
279    #[serde(rename = "x402Version")]
280    pub x402_version: u32,
281    /// Additional error details
282    #[serde(skip_serializing_if = "Option::is_none")]
283    pub details: Option<serde_json::Value>,
284}
285
286impl ErrorResponse {
287    /// Create a new error response from X402Error
288    pub fn from_x402_error(error: &X402Error) -> Self {
289        Self {
290            error: error.to_string(),
291            error_type: error.error_type().to_string(),
292            status_code: error.status_code(),
293            x402_version: 1,
294            details: None,
295        }
296    }
297
298    /// Create a new error response with custom message
299    pub fn new(error: impl Into<String>, error_type: impl Into<String>, status_code: u16) -> Self {
300        Self {
301            error: error.into(),
302            error_type: error_type.into(),
303            status_code,
304            x402_version: 1,
305            details: None,
306        }
307    }
308
309    /// Add error details
310    pub fn with_details(mut self, details: serde_json::Value) -> Self {
311        self.details = Some(details);
312        self
313    }
314}
315
316impl From<&X402Error> for ErrorResponse {
317    fn from(error: &X402Error) -> Self {
318        Self::from_x402_error(error)
319    }
320}
321
322#[cfg(feature = "actix-web")]
323impl ResponseError for X402Error {
324    fn error_response(&self) -> HttpResponse {
325        let error_response = ErrorResponse::from_x402_error(self);
326        let status_code = actix_web::http::StatusCode::from_u16(self.status_code())
327            .unwrap_or(actix_web::http::StatusCode::INTERNAL_SERVER_ERROR);
328
329        HttpResponse::build(status_code).json(error_response)
330    }
331}