1use thiserror::Error;
4
5#[cfg(feature = "actix-web")]
6use actix_web::{HttpResponse, ResponseError};
7
8pub type Result<T> = std::result::Result<T, X402Error>;
10
11#[derive(Error, Debug)]
13pub enum X402Error {
14 #[error("JSON error: {0}")]
16 Json(#[from] serde_json::Error),
17
18 #[error("HTTP error: {0}")]
20 Http(#[from] reqwest::Error),
21
22 #[error("Base64 error: {0}")]
24 Base64(#[from] base64::DecodeError),
25
26 #[error("Invalid payment payload: {message}")]
28 InvalidPaymentPayload { message: String },
29
30 #[error("Invalid payment requirements: {message}")]
32 InvalidPaymentRequirements { message: String },
33
34 #[error("Payment verification failed: {reason}")]
36 PaymentVerificationFailed { reason: String },
37
38 #[error("Payment settlement failed: {reason}")]
40 PaymentSettlementFailed { reason: String },
41
42 #[error("Facilitator error: {message}")]
44 FacilitatorError { message: String },
45
46 #[error("Cryptographic error: {0}")]
48 Crypto(#[from] Box<dyn std::error::Error + Send + Sync>),
49
50 #[error("Invalid signature: {message}")]
52 InvalidSignature { message: String },
53
54 #[error("Invalid authorization: {message}")]
56 InvalidAuthorization { message: String },
57
58 #[error("Network not supported: {network}")]
60 NetworkNotSupported { network: String },
61
62 #[error("Network error: {message}")]
64 NetworkError { message: String },
65
66 #[error("Invalid network: {message}")]
68 InvalidNetwork { message: String },
69
70 #[error("Scheme not supported: {scheme}")]
72 SchemeNotSupported { scheme: String },
73
74 #[error("Insufficient funds")]
76 InsufficientFunds,
77
78 #[error("Authorization expired")]
80 AuthorizationExpired,
81
82 #[error("Authorization not yet valid")]
84 AuthorizationNotYetValid,
85
86 #[error("Invalid amount: expected {expected}, got {got}")]
88 InvalidAmount { expected: String, got: String },
89
90 #[error("Recipient mismatch: expected {expected}, got {got}")]
92 RecipientMismatch { expected: String, got: String },
93
94 #[error("Unexpected error: {message}")]
96 Unexpected { message: String },
97
98 #[error("Configuration error: {message}")]
100 Config { message: String },
101
102 #[error("Request timeout")]
104 Timeout,
105
106 #[error("IO error: {0}")]
108 Io(#[from] std::io::Error),
109
110 #[error("Parse integer error: {0}")]
112 ParseInt(#[from] std::num::ParseIntError),
113
114 #[error("From hex error: {0}")]
116 FromHex(#[from] rustc_hex::FromHexError),
117
118 #[error("From str radix error: {0}")]
120 FromStrRadix(#[from] ethereum_types::FromStrRadixErr),
121
122 #[cfg(feature = "http3")]
124 #[error("Rustls error: {0}")]
125 Rustls(#[from] rustls::Error),
126
127 #[cfg(feature = "http3")]
129 #[error("Certificate generation error: {0}")]
130 CertGen(#[from] rcgen::Error),
131
132 #[error("Address parse error: {0}")]
134 AddrParse(#[from] std::net::AddrParseError),
135
136 #[cfg(feature = "http3")]
138 #[error("Quinn connection error: {0}")]
139 Quinn(#[from] quinn::ConnectionError),
140
141 #[cfg(feature = "http3")]
143 #[error("H3 connection error: {0}")]
144 H3(#[from] h3::error::ConnectionError),
145}
146
147impl X402Error {
148 pub fn invalid_payment_payload(message: impl Into<String>) -> Self {
150 Self::InvalidPaymentPayload {
151 message: message.into(),
152 }
153 }
154
155 pub fn invalid_payment_requirements(message: impl Into<String>) -> Self {
157 Self::InvalidPaymentRequirements {
158 message: message.into(),
159 }
160 }
161
162 pub fn payment_verification_failed(reason: impl Into<String>) -> Self {
164 Self::PaymentVerificationFailed {
165 reason: reason.into(),
166 }
167 }
168
169 pub fn payment_settlement_failed(reason: impl Into<String>) -> Self {
171 Self::PaymentSettlementFailed {
172 reason: reason.into(),
173 }
174 }
175
176 pub fn facilitator_error(message: impl Into<String>) -> Self {
178 Self::FacilitatorError {
179 message: message.into(),
180 }
181 }
182
183 pub fn invalid_signature(message: impl Into<String>) -> Self {
185 Self::InvalidSignature {
186 message: message.into(),
187 }
188 }
189
190 pub fn invalid_authorization(message: impl Into<String>) -> Self {
192 Self::InvalidAuthorization {
193 message: message.into(),
194 }
195 }
196
197 pub fn network_error(message: impl Into<String>) -> Self {
199 Self::NetworkError {
200 message: message.into(),
201 }
202 }
203
204 pub fn invalid_network(message: impl Into<String>) -> Self {
206 Self::InvalidNetwork {
207 message: message.into(),
208 }
209 }
210
211 pub fn unexpected(message: impl Into<String>) -> Self {
213 Self::Unexpected {
214 message: message.into(),
215 }
216 }
217
218 pub fn config(message: impl Into<String>) -> Self {
220 Self::Config {
221 message: message.into(),
222 }
223 }
224
225 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 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#[derive(Debug, Clone, serde::Serialize)]
312pub struct ErrorResponse {
313 pub error: String,
315 #[serde(rename = "type")]
317 pub error_type: String,
318 pub status_code: u16,
320 #[serde(rename = "x402Version")]
322 pub x402_version: u32,
323 #[serde(skip_serializing_if = "Option::is_none")]
325 pub details: Option<serde_json::Value>,
326}
327
328impl ErrorResponse {
329 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 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 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}