webserver_base/telegram/
error.rs1use std::fmt::{Debug, Formatter};
2use std::time::Duration;
3use std::{error, fmt};
4
5#[derive(Debug)]
12pub enum TelegramError {
13 InvalidToken(String),
15
16 MessageTooLarge {
22 bytes: usize,
24 max: usize,
26 },
27
28 Http(reqwest::Error),
30
31 Api {
33 error_code: i32,
35 description: String,
37 },
38
39 RateLimited {
45 retry_after: Duration,
47 },
48
49 Serialization(serde_json::Error),
51
52 Internal(String),
54}
55
56impl TelegramError {
57 #[must_use]
64 pub const fn is_permanent(&self) -> bool {
65 match self {
66 Self::InvalidToken(_) | Self::MessageTooLarge { .. } | Self::Internal(_) => true,
67 Self::Api { error_code, .. } => {
68 *error_code >= 400 && *error_code < 500 && *error_code != 429
71 }
72 Self::Http(_) | Self::Serialization(_) | Self::RateLimited { .. } => false,
73 }
74 }
75
76 #[must_use]
84 pub fn is_misconfiguration(&self) -> bool {
85 match self {
86 Self::InvalidToken(_) => true,
87 Self::Api {
88 error_code,
89 description,
90 } => {
91 *error_code == 401
92 || *error_code == 403
93 || (*error_code == 400 && description.to_lowercase().contains("chat not found"))
94 }
95 _ => false,
96 }
97 }
98}
99
100impl error::Error for TelegramError {}
101
102impl fmt::Display for TelegramError {
103 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
104 match self {
105 Self::InvalidToken(reason) => write!(f, "invalid telegram bot token: {reason}"),
106 Self::MessageTooLarge { bytes, max } => {
107 write!(
108 f,
109 "message is {bytes} bytes, which exceeds the {max} byte maximum"
110 )
111 }
112 Self::Http(error) => write!(f, "telegram http request failed: {error}"),
113 Self::Api {
114 error_code,
115 description,
116 } => write!(
117 f,
118 "telegram rejected the request ({error_code}): {description}"
119 ),
120 Self::RateLimited { retry_after } => {
121 write!(f, "telegram rate limited us; retry after {retry_after:?}")
122 }
123 Self::Serialization(error) => {
124 write!(f, "could not deserialize telegram's response: {error}")
125 }
126 Self::Internal(reason) => write!(f, "internal telegram client error: {reason}"),
127 }
128 }
129}
130
131impl From<reqwest::Error> for TelegramError {
132 fn from(error: reqwest::Error) -> Self {
133 Self::Http(error.without_url())
135 }
136}
137
138impl From<serde_json::Error> for TelegramError {
139 fn from(error: serde_json::Error) -> Self {
140 Self::Serialization(error)
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::TelegramError;
147
148 #[test]
149 fn client_errors_are_permanent() {
150 let error: TelegramError = TelegramError::Api {
151 error_code: 400,
152 description: String::from("Bad Request: message text is empty"),
153 };
154
155 assert!(error.is_permanent());
156 }
157
158 #[test]
159 fn rate_limit_errors_are_not_permanent() {
160 let error: TelegramError = TelegramError::Api {
161 error_code: 429,
162 description: String::from("Too Many Requests: retry after 5"),
163 };
164
165 assert!(!error.is_permanent());
166 }
167
168 #[test]
169 fn server_errors_are_not_permanent() {
170 let error: TelegramError = TelegramError::Api {
171 error_code: 502,
172 description: String::from("Bad Gateway"),
173 };
174
175 assert!(!error.is_permanent());
176 }
177
178 #[test]
179 fn unauthorized_is_a_misconfiguration() {
180 let error: TelegramError = TelegramError::Api {
181 error_code: 401,
182 description: String::from("Unauthorized"),
183 };
184
185 assert!(error.is_misconfiguration());
186 }
187
188 #[test]
189 fn forbidden_is_a_misconfiguration() {
190 let error: TelegramError = TelegramError::Api {
191 error_code: 403,
192 description: String::from("Forbidden: bot was blocked by the user"),
193 };
194
195 assert!(error.is_misconfiguration());
196 }
197
198 #[test]
199 fn chat_not_found_is_a_misconfiguration() {
200 let error: TelegramError = TelegramError::Api {
201 error_code: 400,
202 description: String::from("Bad Request: chat not found"),
203 };
204
205 assert!(error.is_misconfiguration());
206 }
207
208 #[test]
209 fn an_ordinary_bad_request_is_not_a_misconfiguration() {
210 let error: TelegramError = TelegramError::Api {
211 error_code: 400,
212 description: String::from("Bad Request: message is too long"),
213 };
214
215 assert!(!error.is_misconfiguration());
216 }
217
218 #[test]
219 fn display_includes_the_error_code_and_description() {
220 let error: TelegramError = TelegramError::Api {
221 error_code: 400,
222 description: String::from("Bad Request: chat not found"),
223 };
224
225 let expected: String =
226 String::from("telegram rejected the request (400): Bad Request: chat not found");
227 let actual: String = format!("{error}");
228 assert_eq!(expected, actual);
229 }
230}