ant_quic/
error_handling.rs1use thiserror::Error;
14
15#[derive(Error, Debug)]
17pub enum AntQuicError {
18 #[error("Transport error: {0}")]
20 Transport(#[from] crate::transport_error::Error),
21
22 #[error("Connection error: {0}")]
24 Connection(#[from] crate::connection::ConnectionError),
25
26 #[error("Discovery error: {0}")]
28 Discovery(#[from] crate::candidate_discovery::DiscoveryError),
29
30 #[error("NAT traversal error: {0}")]
32 NatTraversal(#[from] crate::nat_traversal_api::NatTraversalError),
33
34 #[error("Configuration error: {0}")]
36 Config(String),
37
38 #[error("I/O error: {0}")]
40 Io(#[from] std::io::Error),
41
42 #[error("Crypto error: {0}")]
44 Crypto(String),
45
46 #[error("PQC error: {0}")]
48 Pqc(#[from] crate::crypto::pqc::types::PqcError),
49
50 #[error("Operation timed out: {0}")]
52 Timeout(String),
53
54 #[error("Resource exhausted: {0}")]
56 ResourceExhausted(String),
57
58 #[error("Invalid parameter: {0}")]
60 InvalidParameter(String),
61
62 #[error("Internal error: {0}")]
64 Internal(String),
65}
66
67pub type Result<T> = std::result::Result<T, AntQuicError>;
69
70pub mod utils {
72 use super::*;
73 use tracing::{debug, error, info, warn};
74
75 pub fn log_error(error: &(dyn std::error::Error + 'static), context: &str) {
77 let error_msg = format!("{}: {}", context, error);
78 match error.downcast_ref::<AntQuicError>() {
79 Some(AntQuicError::Internal(_)) => error!("{}", error_msg),
80 Some(AntQuicError::Transport(_)) => warn!("{}", error_msg),
81 Some(AntQuicError::Connection(_)) => warn!("{}", error_msg),
82 Some(AntQuicError::Timeout(_)) => info!("{}", error_msg),
83 Some(AntQuicError::InvalidParameter(_)) => debug!("{}", error_msg),
84 _ => warn!("{}", error_msg),
85 }
86 }
87
88 pub fn to_user_message(error: &(dyn std::error::Error + 'static)) -> String {
90 match error.downcast_ref::<AntQuicError>() {
91 Some(AntQuicError::Transport(_)) => {
92 "Network connection error. Please check your internet connection.".to_string()
93 }
94 Some(AntQuicError::Connection(_)) => {
95 "Failed to establish connection. The remote peer may be unreachable.".to_string()
96 }
97 Some(AntQuicError::Discovery(_)) => {
98 "Failed to discover network configuration. Please check your network settings."
99 .to_string()
100 }
101 Some(AntQuicError::NatTraversal(_)) => {
102 "NAT traversal failed. This may be due to restrictive network policies.".to_string()
103 }
104 Some(AntQuicError::Timeout(_)) => "Operation timed out. Please try again.".to_string(),
105 Some(AntQuicError::Config(_)) => {
106 "Configuration error. Please check your settings.".to_string()
107 }
108 Some(AntQuicError::Io(_)) => {
109 "System I/O error. Please check file permissions and disk space.".to_string()
110 }
111 Some(AntQuicError::Crypto(_)) => {
112 "Cryptographic operation failed. This may indicate a security issue.".to_string()
113 }
114 Some(AntQuicError::Pqc(_)) => {
115 "Post-quantum cryptographic operation failed.".to_string()
116 }
117 Some(AntQuicError::ResourceExhausted(_)) => {
118 "System resources exhausted. Please close some applications and try again."
119 .to_string()
120 }
121 Some(AntQuicError::InvalidParameter(_)) => {
122 "Invalid input parameters provided.".to_string()
123 }
124 Some(AntQuicError::Internal(_)) => {
125 "An internal error occurred. Please report this issue.".to_string()
126 }
127 _ => format!("An unexpected error occurred: {}", error),
128 }
129 }
130
131 pub fn is_recoverable(error: &(dyn std::error::Error + 'static)) -> bool {
133 match error.downcast_ref::<AntQuicError>() {
134 Some(AntQuicError::Timeout(_)) => true,
135 Some(AntQuicError::Connection(_)) => true,
136 Some(AntQuicError::Discovery(_)) => true,
137 Some(AntQuicError::NatTraversal(_)) => true,
138 Some(AntQuicError::Io(io_err)) => {
139 matches!(
141 io_err.kind(),
142 std::io::ErrorKind::TimedOut | std::io::ErrorKind::Interrupted
143 )
144 }
145 _ => false,
146 }
147 }
148
149 pub fn get_retry_delay(
151 error: &(dyn std::error::Error + 'static),
152 ) -> Option<std::time::Duration> {
153 match error.downcast_ref::<AntQuicError>() {
154 Some(AntQuicError::Timeout(_)) => Some(std::time::Duration::from_millis(100)),
155 Some(AntQuicError::Connection(_)) => Some(std::time::Duration::from_millis(500)),
156 Some(AntQuicError::Discovery(_)) => Some(std::time::Duration::from_secs(1)),
157 Some(AntQuicError::NatTraversal(_)) => Some(std::time::Duration::from_secs(2)),
158 Some(AntQuicError::Io(io_err)) => match io_err.kind() {
159 std::io::ErrorKind::TimedOut => Some(std::time::Duration::from_millis(100)),
160 std::io::ErrorKind::Interrupted => Some(std::time::Duration::from_millis(10)),
161 _ => None,
162 },
163 _ => None,
164 }
165 }
166}
167
168#[macro_export]
170macro_rules! ensure {
171 ($condition:expr, $error:expr) => {
172 if !($condition) {
173 return Err($error.into());
174 }
175 };
176}
177
178#[macro_export]
181macro_rules! bail {
182 ($error:expr) => {
183 return Err($error.into());
184 };
185}
186
187#[macro_export]
190macro_rules! context {
191 ($result:expr, $context:expr) => {
192 $result.map_err(|e| AntQuicError::Internal(format!("{}: {}", $context, e)))
193 };
194}
195
196#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
215 fn error_transport_display() {
216 let err = AntQuicError::Transport(crate::transport_error::Error::INTERNAL_ERROR("test"));
217 let display = format!("{err}");
218 assert!(display.contains("Transport error"));
219 }
220
221 #[test]
222 fn error_connection_display() {
223 let err = AntQuicError::Connection(crate::connection::ConnectionError::LocallyClosed);
224 let display = format!("{err}");
225 assert!(display.contains("Connection error"));
226 }
227
228 #[test]
229 fn error_config_display() {
230 let err = AntQuicError::Config("bad config".to_string());
231 let display = format!("{err}");
232 assert!(display.contains("Configuration error"));
233 assert!(display.contains("bad config"));
234 }
235
236 #[test]
237 fn error_crypto_display() {
238 let err = AntQuicError::Crypto("key error".to_string());
239 let display = format!("{err}");
240 assert!(display.contains("Crypto error"));
241 }
242
243 #[test]
244 fn error_timeout_display() {
245 let err = AntQuicError::Timeout("timed out".to_string());
246 let display = format!("{err}");
247 assert!(display.contains("timed out"));
248 }
249
250 #[test]
251 fn error_resource_exhausted_display() {
252 let err = AntQuicError::ResourceExhausted("no mem".to_string());
253 let display = format!("{err}");
254 assert!(display.contains("Resource exhausted"));
255 }
256
257 #[test]
258 fn error_invalid_parameter_display() {
259 let err = AntQuicError::InvalidParameter("bad param".to_string());
260 let display = format!("{err}");
261 assert!(display.contains("Invalid parameter"));
262 }
263
264 #[test]
265 fn error_internal_display() {
266 let err = AntQuicError::Internal("bug".to_string());
267 let display = format!("{err}");
268 assert!(display.contains("Internal error"));
269 }
270
271 #[test]
272 fn error_debug_format() {
273 let err = AntQuicError::Internal("test".to_string());
274 let debug = format!("{err:?}");
275 assert!(debug.contains("Internal"));
276 }
277
278 #[test]
279 fn error_from_invalid_parameter() {
280 let s = "bad".to_string();
281 let err: AntQuicError = AntQuicError::InvalidParameter(s);
282 assert!(format!("{err}").contains("Invalid parameter"));
283 }
284
285 #[test]
286 fn error_io_into() {
287 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
288 let err: AntQuicError = io_err.into();
289 assert!(matches!(err, AntQuicError::Io(_)));
290 }
291
292 #[test]
295 fn log_error_internal() {
296 let err = AntQuicError::Internal("test internal".to_string());
297 utils::log_error(&err, "test context");
299 }
300
301 #[test]
302 fn log_error_transport() {
303 let err = AntQuicError::Transport(crate::transport_error::Error::INTERNAL_ERROR("x"));
304 utils::log_error(&err, "transport");
305 }
306
307 #[test]
308 fn log_error_generic() {
309 let err = std::io::Error::other("generic");
310 utils::log_error(&err, "generic context");
311 }
312
313 #[test]
316 fn user_message_transport() {
317 let err = AntQuicError::Transport(crate::transport_error::Error::INTERNAL_ERROR(""));
318 let msg = utils::to_user_message(&err);
319 assert!(msg.contains("Network connection error"));
320 }
321
322 #[test]
323 fn user_message_connection() {
324 let err = AntQuicError::Connection(crate::connection::ConnectionError::LocallyClosed);
325 let msg = utils::to_user_message(&err);
326 assert!(msg.contains("Failed to establish connection"));
327 }
328
329 #[test]
330 fn user_message_timeout() {
331 let err = AntQuicError::Timeout("x".to_string());
332 let msg = utils::to_user_message(&err);
333 assert!(msg.contains("timed out"));
334 }
335
336 #[test]
337 fn user_message_crypto() {
338 let err = AntQuicError::Crypto("x".to_string());
339 let msg = utils::to_user_message(&err);
340 assert!(msg.contains("Cryptographic operation failed"));
341 }
342
343 #[test]
344 fn user_message_internal() {
345 let err = AntQuicError::Internal("x".to_string());
346 let msg = utils::to_user_message(&err);
347 assert!(msg.contains("internal error"));
348 }
349
350 #[test]
351 fn user_message_config() {
352 let err = AntQuicError::Config("x".to_string());
353 let msg = utils::to_user_message(&err);
354 assert!(msg.contains("Configuration error"));
355 }
356
357 #[test]
358 fn user_message_discovery() {
359 let err = std::io::Error::other("weird");
361 let msg = utils::to_user_message(&err);
362 assert!(msg.contains("unexpected error"));
363 }
364
365 #[test]
366 fn user_message_io() {
367 let err = AntQuicError::Io(std::io::Error::new(
368 std::io::ErrorKind::PermissionDenied,
369 "denied",
370 ));
371 let msg = utils::to_user_message(&err);
372 assert!(msg.contains("I/O error"));
373 }
374
375 #[test]
376 fn user_message_invalid_parameter() {
377 let err = AntQuicError::InvalidParameter("x".to_string());
378 let msg = utils::to_user_message(&err);
379 assert!(msg.contains("Invalid input"));
380 }
381
382 #[test]
383 fn user_message_resource_exhausted() {
384 let err = AntQuicError::ResourceExhausted("x".to_string());
385 let msg = utils::to_user_message(&err);
386 assert!(msg.contains("resources exhausted"));
387 }
388
389 #[test]
390 fn user_message_pqc() {
391 use crate::crypto::pqc::types::PqcError;
392 let err = AntQuicError::Pqc(PqcError::KeyGenerationFailed("test".to_string()));
393 let msg = utils::to_user_message(&err);
394 assert!(msg.contains("Post-quantum"));
395 }
396
397 #[test]
398 fn user_message_nat_traversal() {
399 use crate::nat_traversal_api::NatTraversalError;
400 let err = AntQuicError::NatTraversal(NatTraversalError::HolePunchingFailed);
401 let msg = utils::to_user_message(&err);
402 assert!(msg.contains("NAT traversal failed"));
403 }
404
405 #[test]
408 fn timeout_is_recoverable() {
409 let err = AntQuicError::Timeout("x".to_string());
410 assert!(utils::is_recoverable(&err));
411 }
412
413 #[test]
414 fn connection_error_is_recoverable() {
415 let err = AntQuicError::Connection(crate::connection::ConnectionError::LocallyClosed);
416 assert!(utils::is_recoverable(&err));
417 }
418
419 #[test]
420 fn internal_error_is_not_recoverable() {
421 let err = AntQuicError::Internal("x".to_string());
422 assert!(!utils::is_recoverable(&err));
423 }
424
425 #[test]
426 fn config_error_is_not_recoverable() {
427 let err = AntQuicError::Config("x".to_string());
428 assert!(!utils::is_recoverable(&err));
429 }
430
431 #[test]
432 fn io_timeout_is_recoverable() {
433 let err = AntQuicError::Io(std::io::Error::new(std::io::ErrorKind::TimedOut, "timeout"));
434 assert!(utils::is_recoverable(&err));
435 }
436
437 #[test]
438 fn io_interrupted_is_recoverable() {
439 let err = AntQuicError::Io(std::io::Error::new(
440 std::io::ErrorKind::Interrupted,
441 "interrupted",
442 ));
443 assert!(utils::is_recoverable(&err));
444 }
445
446 #[test]
447 fn io_permission_denied_is_not_recoverable() {
448 let err = AntQuicError::Io(std::io::Error::new(
449 std::io::ErrorKind::PermissionDenied,
450 "denied",
451 ));
452 assert!(!utils::is_recoverable(&err));
453 }
454
455 #[test]
456 fn generic_error_is_not_recoverable() {
457 let err = std::io::Error::other("other");
458 assert!(!utils::is_recoverable(&err));
459 }
460
461 #[test]
462 fn crypto_error_is_not_recoverable() {
463 let err = AntQuicError::Crypto("x".to_string());
464 assert!(!utils::is_recoverable(&err));
465 }
466
467 #[test]
470 fn timeout_has_retry_delay() {
471 let err = AntQuicError::Timeout("x".to_string());
472 let delay = utils::get_retry_delay(&err);
473 assert!(delay.is_some());
474 assert_eq!(delay.unwrap(), std::time::Duration::from_millis(100));
475 }
476
477 #[test]
478 fn connection_has_retry_delay() {
479 let err = AntQuicError::Connection(crate::connection::ConnectionError::LocallyClosed);
480 let delay = utils::get_retry_delay(&err);
481 assert!(delay.is_some());
482 assert_eq!(delay.unwrap(), std::time::Duration::from_millis(500));
483 }
484
485 #[test]
486 fn discovery_has_retry_delay() {
487 use crate::candidate_discovery::DiscoveryError;
488 let err = AntQuicError::Discovery(DiscoveryError::NoLocalInterfaces);
489 let delay = utils::get_retry_delay(&err);
490 assert!(delay.is_some());
491 }
492
493 #[test]
494 fn internal_has_no_retry_delay() {
495 let err = AntQuicError::Internal("x".to_string());
496 assert!(utils::get_retry_delay(&err).is_none());
497 }
498
499 #[test]
500 fn generic_error_has_no_retry_delay() {
501 let err = std::io::Error::other("other");
502 assert!(utils::get_retry_delay(&err).is_none());
503 }
504
505 #[test]
506 fn io_timeout_has_retry_delay() {
507 let err = AntQuicError::Io(std::io::Error::new(std::io::ErrorKind::TimedOut, "timeout"));
508 let delay = utils::get_retry_delay(&err);
509 assert!(delay.is_some());
510 assert_eq!(delay.unwrap(), std::time::Duration::from_millis(100));
511 }
512
513 #[test]
514 fn io_interrupted_has_retry_delay() {
515 let err = AntQuicError::Io(std::io::Error::new(std::io::ErrorKind::Interrupted, "int"));
516 let delay = utils::get_retry_delay(&err);
517 assert!(delay.is_some());
518 assert_eq!(delay.unwrap(), std::time::Duration::from_millis(10));
519 }
520
521 #[test]
524 fn ensure_passes_when_true() {
525 let result: Result<()> = (|| {
526 ensure!(
527 true,
528 AntQuicError::Internal("should not happen".to_string())
529 );
530 Ok(())
531 })();
532 assert!(result.is_ok());
533 }
534
535 #[test]
536 fn ensure_fails_when_false() {
537 let result: Result<()> = (|| {
538 ensure!(false, AntQuicError::InvalidParameter("bad".to_string()));
539 Ok(())
540 })();
541 assert!(result.is_err());
542 assert!(matches!(
543 result.unwrap_err(),
544 AntQuicError::InvalidParameter(_)
545 ));
546 }
547
548 #[test]
549 fn bail_returns_error() {
550 let result: Result<()> = (|| {
551 bail!(AntQuicError::Internal("bailed".to_string()));
552 })();
553 assert!(result.is_err());
554 assert!(format!("{}", result.unwrap_err()).contains("bailed"));
555 }
556
557 #[test]
558 fn bail_with_type_conversion() {
559 let result: Result<()> = (|| {
561 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "missing");
562 bail!(io_err);
563 })();
564 assert!(result.is_err());
565 assert!(matches!(result.unwrap_err(), AntQuicError::Io(_)));
566 }
567
568 #[test]
571 fn error_source_chain_transport() {
572 let err = AntQuicError::Transport(crate::transport_error::Error::INTERNAL_ERROR("chain"));
573 let source = std::error::Error::source(&err);
574 assert!(source.is_some());
575 }
576}