1use std::fmt;
6
7pub type Result<T> = std::result::Result<T, BotError>;
9
10#[derive(Debug, thiserror::Error)]
12pub enum BotError {
13 #[error("HTTP error: {0}")]
15 Http(#[from] reqwest::Error),
16
17 #[error("WebSocket error: {0}")]
19 WebSocket(Box<tokio_tungstenite::tungstenite::Error>),
20
21 #[error("JSON error: {0}")]
23 Json(#[from] serde_json::Error),
24
25 #[error("URL error: {0}")]
27 Url(#[from] url::ParseError),
28
29 #[error("API error: {code} - {message}")]
31 Api { code: u32, message: String },
32
33 #[error("Authentication failed: {0}")]
35 AuthenticationFailed(String),
36
37 #[error("Not found: {0}")]
39 NotFound(String),
40
41 #[error("Method not allowed: {0}")]
43 MethodNotAllowed(String),
44
45 #[error("Forbidden: {0}")]
47 Forbidden(String),
48
49 #[error("Sequence number error: {0}")]
51 SequenceNumber(String),
52
53 #[error("Server error: {0}")]
55 Server(String),
56
57 #[error("Authentication error: {0}")]
59 Auth(String),
60
61 #[error("Connection error: {0}")]
63 Connection(String),
64
65 #[error("Rate limited: retry after {retry_after} seconds")]
67 RateLimit { retry_after: u64 },
68
69 #[error("Invalid configuration: {0}")]
71 Config(String),
72
73 #[error("Invalid data: {0}")]
75 InvalidData(String),
76
77 #[error("Network timeout")]
79 Timeout,
80
81 #[error("Gateway error: {0}")]
83 Gateway(String),
84
85 #[error("Session error: {0}")]
87 Session(String),
88
89 #[error("Internal error: {0}")]
91 Internal(String),
92
93 #[error("IO error: {0}")]
95 Io(#[from] std::io::Error),
96
97 #[error("Not implemented: {0}")]
99 NotImplemented(String),
100}
101
102impl BotError {
103 pub fn api(code: u32, message: impl Into<String>) -> Self {
105 Self::Api {
106 code,
107 message: message.into(),
108 }
109 }
110
111 pub fn auth(message: impl Into<String>) -> Self {
113 Self::Auth(message.into())
114 }
115
116 pub fn connection(message: impl Into<String>) -> Self {
118 Self::Connection(message.into())
119 }
120
121 pub fn config(message: impl Into<String>) -> Self {
123 Self::Config(message.into())
124 }
125
126 pub fn invalid_data(message: impl Into<String>) -> Self {
128 Self::InvalidData(message.into())
129 }
130
131 pub fn gateway(message: impl Into<String>) -> Self {
133 Self::Gateway(message.into())
134 }
135
136 pub fn session(message: impl Into<String>) -> Self {
138 Self::Session(message.into())
139 }
140
141 pub fn internal(message: impl Into<String>) -> Self {
143 Self::Internal(message.into())
144 }
145
146 pub fn rate_limit(retry_after: u64) -> Self {
148 Self::RateLimit { retry_after }
149 }
150
151 pub fn not_implemented(message: impl Into<String>) -> Self {
153 Self::NotImplemented(message.into())
154 }
155
156 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 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
182pub trait IntoBotError<T> {
184 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
197impl 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
204pub 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}