Skip to main content

botrs/
error.rs

1//! Error types for the BotRS library.
2//!
3//! This module defines all the error types that can occur when using the BotRS framework.
4
5use std::fmt;
6
7/// A specialized Result type for BotRS operations.
8pub type Result<T> = std::result::Result<T, BotError>;
9
10/// The main error type for BotRS operations.
11#[derive(Debug, thiserror::Error)]
12pub enum BotError {
13    /// HTTP client errors
14    #[error("HTTP error: {0}")]
15    Http(#[from] reqwest::Error),
16
17    /// WebSocket connection errors
18    #[error("WebSocket error: {0}")]
19    WebSocket(Box<tokio_tungstenite::tungstenite::Error>),
20
21    /// JSON serialization/deserialization errors
22    #[error("JSON error: {0}")]
23    Json(#[from] serde_json::Error),
24
25    /// URL parsing errors
26    #[error("URL error: {0}")]
27    Url(#[from] url::ParseError),
28
29    /// API response errors
30    #[error("API error: {code} - {message}")]
31    Api { code: u32, message: String },
32
33    /// Authentication failed (401)
34    #[error("Authentication failed: {0}")]
35    AuthenticationFailed(String),
36
37    /// Not found (404)
38    #[error("Not found: {0}")]
39    NotFound(String),
40
41    /// Method not allowed (405)
42    #[error("Method not allowed: {0}")]
43    MethodNotAllowed(String),
44
45    /// Forbidden (403)
46    #[error("Forbidden: {0}")]
47    Forbidden(String),
48
49    /// Sequence number error (429)
50    #[error("Sequence number error: {0}")]
51    SequenceNumber(String),
52
53    /// Server error (500, 504)
54    #[error("Server error: {0}")]
55    Server(String),
56
57    /// Authentication errors
58    #[error("Authentication error: {0}")]
59    Auth(String),
60
61    /// Connection errors
62    #[error("Connection error: {0}")]
63    Connection(String),
64
65    /// Rate limiting errors
66    #[error("Rate limited: retry after {retry_after} seconds")]
67    RateLimit { retry_after: u64 },
68
69    /// Invalid configuration errors
70    #[error("Invalid configuration: {0}")]
71    Config(String),
72
73    /// Invalid data format errors
74    #[error("Invalid data: {0}")]
75    InvalidData(String),
76
77    /// Network timeout errors
78    #[error("Network timeout")]
79    Timeout,
80
81    /// Gateway errors
82    #[error("Gateway error: {0}")]
83    Gateway(String),
84
85    /// Session errors
86    #[error("Session error: {0}")]
87    Session(String),
88
89    /// Generic errors
90    #[error("Internal error: {0}")]
91    Internal(String),
92
93    /// IO errors
94    #[error("IO error: {0}")]
95    Io(#[from] std::io::Error),
96
97    /// Not implemented errors
98    #[error("Not implemented: {0}")]
99    NotImplemented(String),
100}
101
102impl BotError {
103    /// Creates a new API error.
104    pub fn api(code: u32, message: impl Into<String>) -> Self {
105        Self::Api {
106            code,
107            message: message.into(),
108        }
109    }
110
111    /// Creates a new authentication error.
112    pub fn auth(message: impl Into<String>) -> Self {
113        Self::Auth(message.into())
114    }
115
116    /// Creates a new connection error.
117    pub fn connection(message: impl Into<String>) -> Self {
118        Self::Connection(message.into())
119    }
120
121    /// Creates a new configuration error.
122    pub fn config(message: impl Into<String>) -> Self {
123        Self::Config(message.into())
124    }
125
126    /// Creates a new invalid data error.
127    pub fn invalid_data(message: impl Into<String>) -> Self {
128        Self::InvalidData(message.into())
129    }
130
131    /// Creates a new gateway error.
132    pub fn gateway(message: impl Into<String>) -> Self {
133        Self::Gateway(message.into())
134    }
135
136    /// Creates a new session error.
137    pub fn session(message: impl Into<String>) -> Self {
138        Self::Session(message.into())
139    }
140
141    /// Creates a new internal error.
142    pub fn internal(message: impl Into<String>) -> Self {
143        Self::Internal(message.into())
144    }
145
146    /// Creates a new rate limit error.
147    pub fn rate_limit(retry_after: u64) -> Self {
148        Self::RateLimit { retry_after }
149    }
150
151    /// Creates a new not implemented error.
152    pub fn not_implemented(message: impl Into<String>) -> Self {
153        Self::NotImplemented(message.into())
154    }
155
156    /// Returns true if this error is retryable.
157    pub fn is_retryable(&self) -> bool {
158        match self {
159            BotError::Http(e) => e.is_timeout() || e.is_connect(),
160            BotError::WebSocket(_) => true,
161            BotError::Connection(_) => true,
162            BotError::Timeout => true,
163            BotError::Gateway(_) => true,
164            BotError::RateLimit { .. } => true,
165            _ => false,
166        }
167    }
168
169    /// Returns the retry delay in seconds if this error is retryable.
170    pub fn retry_after(&self) -> Option<u64> {
171        match self {
172            BotError::RateLimit { retry_after } => Some(*retry_after),
173            BotError::Connection(_) => Some(5),
174            BotError::Gateway(_) => Some(1),
175            BotError::Timeout => Some(3),
176            _ if self.is_retryable() => Some(1),
177            _ => None,
178        }
179    }
180}
181
182/// Extension trait for converting generic errors to BotError.
183pub trait IntoBotError<T> {
184    /// Converts the result into a BotError with context.
185    fn with_context(self, context: &str) -> Result<T>;
186}
187
188impl<T, E> IntoBotError<T> for std::result::Result<T, E>
189where
190    E: fmt::Display,
191{
192    fn with_context(self, context: &str) -> Result<T> {
193        self.map_err(|e| BotError::internal(format!("{context}: {e}")))
194    }
195}
196
197// Manual From implementation for boxing WebSocket error
198impl From<tokio_tungstenite::tungstenite::Error> for BotError {
199    fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
200        BotError::WebSocket(Box::new(err))
201    }
202}
203
204/// Maps HTTP status codes to specific error types.
205pub fn http_error_from_status(status: u16, message: String) -> BotError {
206    match status {
207        401 => BotError::AuthenticationFailed(message),
208        403 => BotError::Forbidden(message),
209        404 => BotError::NotFound(message),
210        405 => BotError::MethodNotAllowed(message),
211        429 => BotError::SequenceNumber(message),
212        500 | 504 => BotError::Server(message),
213        _ => BotError::api(status as u32, message),
214    }
215}