Skip to main content

bel7_axum/errors/
traits.rs

1// Copyright (C) 2025-2026 Michael S. Klishin and Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Core error classification traits.
16//!
17//! These traits provide a common vocabulary for describing error behavior
18//! across different error types in your application.
19
20use std::error::Error;
21
22/// Trait for errors that can be classified as recoverable or not.
23///
24/// Recoverable errors are typically transient failures that may succeed
25/// on retry (timeouts, temporary network issues, etc.).
26///
27/// # Example
28///
29/// ```
30/// use bel7_axum::RecoverableError;
31/// use thiserror::Error;
32///
33/// #[derive(Error, Debug)]
34/// enum MyError {
35///     #[error("connection timeout")]
36///     Timeout,
37///     #[error("invalid input: {0}")]
38///     InvalidInput(String),
39/// }
40///
41/// impl RecoverableError for MyError {
42///     fn is_recoverable(&self) -> bool {
43///         matches!(self, MyError::Timeout)
44///     }
45/// }
46/// ```
47pub trait RecoverableError: Error {
48    /// Returns `true` if this error is potentially recoverable via retry.
49    fn is_recoverable(&self) -> bool;
50}
51
52/// Trait for errors related to network connections.
53///
54/// Useful for connection pool management, reconnection logic, and
55/// distinguishing between connection failures and application errors.
56pub trait ConnectionError: Error {
57    /// Returns `true` if this error indicates the connection was closed.
58    fn is_connection_closed(&self) -> bool;
59
60    /// Returns `true` if this error indicates a connection timeout.
61    fn is_timeout(&self) -> bool {
62        false
63    }
64
65    /// Returns `true` if this error indicates a connection was refused.
66    fn is_connection_refused(&self) -> bool {
67        false
68    }
69}
70
71/// Trait for errors that can provide additional context.
72///
73/// Useful for building rich error messages with suggestions or help text.
74pub trait DiagnosticError: Error {
75    /// Returns suggestions for fixing this error, if any.
76    fn suggestions(&self) -> Vec<String> {
77        Vec::new()
78    }
79
80    /// Returns help text for this error, if any.
81    fn help(&self) -> Option<String> {
82        None
83    }
84
85    /// Returns the position in input where this error occurred, if applicable.
86    fn position(&self) -> Option<usize> {
87        None
88    }
89}
90
91/// Extension trait for checking common error message patterns.
92///
93/// Provides heuristic-based error classification by examining error messages.
94/// Useful when you don't control the error type but need to classify it.
95pub trait ErrorMessageExt {
96    /// Check if the error message suggests a timeout.
97    fn message_suggests_timeout(&self) -> bool;
98
99    /// Check if the error message suggests a closed connection.
100    fn message_suggests_connection_closed(&self) -> bool;
101
102    /// Check if the error message suggests a connection reset.
103    fn message_suggests_connection_reset(&self) -> bool;
104}
105
106impl<E: Error> ErrorMessageExt for E {
107    fn message_suggests_timeout(&self) -> bool {
108        let msg = self.to_string().to_lowercase();
109        msg.contains("timeout") || msg.contains("timed out")
110    }
111
112    fn message_suggests_connection_closed(&self) -> bool {
113        let msg = self.to_string().to_lowercase();
114        msg.contains("closed") || msg.contains("eof") || msg.contains("end of file")
115    }
116
117    fn message_suggests_connection_reset(&self) -> bool {
118        let msg = self.to_string().to_lowercase();
119        msg.contains("reset") || msg.contains("broken pipe") || msg.contains("connection reset")
120    }
121}