grammers_mtsender/errors.rs
1// Copyright 2020 - developers of the `grammers` project.
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9use std::{fmt, io};
10
11use grammers_mtproto::{authentication, mtp, transport};
12use grammers_tl_types as tl;
13
14/// This error occurs when reading from the network fails.
15#[derive(Debug)]
16pub enum ReadError {
17 /// Standard I/O error.
18 Io(io::Error),
19 /// Error propagated from the underlying [`transport`].
20 Transport(transport::Error),
21 /// Error propagated from attempting to deserialize an invalid [`tl::Deserializable`].
22 Deserialize(mtp::DeserializeError),
23}
24
25impl std::error::Error for ReadError {}
26
27impl Clone for ReadError {
28 fn clone(&self) -> Self {
29 match self {
30 Self::Io(e) => Self::Io(
31 e.raw_os_error()
32 .map(io::Error::from_raw_os_error)
33 .unwrap_or_else(|| io::Error::new(e.kind(), e.to_string())),
34 ),
35 Self::Transport(e) => Self::Transport(e.clone()),
36 Self::Deserialize(e) => Self::Deserialize(e.clone()),
37 }
38 }
39}
40
41impl fmt::Display for ReadError {
42 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match self {
44 Self::Io(err) => write!(f, "read error, IO failed: {err}"),
45 Self::Transport(err) => write!(f, "read error, transport-level: {err}"),
46 Self::Deserialize(err) => write!(f, "read error, bad response: {err}"),
47 }
48 }
49}
50
51impl From<io::Error> for ReadError {
52 fn from(error: io::Error) -> Self {
53 Self::Io(error)
54 }
55}
56
57impl From<transport::Error> for ReadError {
58 fn from(error: transport::Error) -> Self {
59 Self::Transport(error)
60 }
61}
62
63impl From<mtp::DeserializeError> for ReadError {
64 fn from(error: mtp::DeserializeError) -> Self {
65 Self::Deserialize(error)
66 }
67}
68
69impl From<tl::deserialize::Error> for ReadError {
70 fn from(error: tl::deserialize::Error) -> Self {
71 Self::Deserialize(error.into())
72 }
73}
74
75/// The error type reported by the server when a request is misused.
76///
77/// These are returned when Telegram respond to an RPC with [`tl::types::RpcError`].
78#[derive(Clone, Debug, PartialEq)]
79pub struct RpcError {
80 /// A numerical value similar to [HTTP response status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status).
81 pub code: i32,
82
83 /// The ASCII error name, normally in screaming snake case.
84 ///
85 /// Digit words are removed from the name and put in the [`RpcError::value`] instead.
86 /// ```
87 /// use grammers_mtsender::RpcError;
88 /// let rpc_error = RpcError::from(grammers_tl_types::types::RpcError {
89 /// error_code: 500, error_message: "INTERDC_2_CALL_ERROR".into() });
90 /// assert_eq!(rpc_error.name, "INTERDC_CALL_ERROR");
91 /// assert_eq!(rpc_error.value, Some(2));
92 /// ```
93 pub name: String,
94
95 /// If the error contained an additional integer value, it will be present here and removed from the [`RpcError::name`].
96 /// ```
97 /// use grammers_mtsender::RpcError;
98 /// let rpc_error = RpcError::from(grammers_tl_types::types::RpcError {
99 /// error_code: 420, error_message: "FLOOD_WAIT_31".into() });
100 /// assert_eq!(rpc_error.name, "FLOOD_WAIT");
101 /// assert_eq!(rpc_error.value, Some(31));
102 /// ```
103 pub value: Option<u32>,
104
105 /// The constructor identifier of the request that triggered this error.
106 /// Won't be present if the error was artificially constructed.
107 pub caused_by: Option<u32>,
108}
109
110impl std::error::Error for RpcError {}
111
112impl fmt::Display for RpcError {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 write!(f, "rpc error {}: {}", self.code, self.name)?;
115 if let Some(caused_by) = self.caused_by {
116 write!(f, " caused by {}", tl::name_for_id(caused_by))?;
117 }
118 if let Some(value) = self.value {
119 write!(f, " (value: {value})")?;
120 }
121 Ok(())
122 }
123}
124
125impl From<tl::types::RpcError> for RpcError {
126 fn from(error: tl::types::RpcError) -> Self {
127 // Extract the numeric value in the error, if any
128 if let Some((value, parsed_value)) = error
129 .error_message
130 .split(|c: char| !c.is_ascii_digit())
131 .flat_map(|value| {
132 value
133 .parse::<u32>()
134 .map(|parsed_value| (value, parsed_value))
135 })
136 .next()
137 {
138 let mut to_remove = String::with_capacity(1 + value.len());
139 to_remove.push('_');
140 to_remove.push_str(value);
141 Self {
142 code: error.error_code,
143 name: error.error_message.replace(&to_remove, ""),
144 value: Some(parsed_value),
145 caused_by: None,
146 }
147 } else {
148 Self {
149 code: error.error_code,
150 name: error.error_message.clone(),
151 value: None,
152 caused_by: None,
153 }
154 }
155 }
156}
157
158impl RpcError {
159 /// Matches on the name of the RPC error (case-sensitive).
160 ///
161 /// Useful in `match` arm guards. A single trailing or leading asterisk (`'*'`) is allowed,
162 /// and will instead check if the error name starts (or ends with) the input parameter.
163 ///
164 /// # Examples
165 ///
166 /// ```
167 /// # let request_result = Result::<(), _>::Err(grammers_mtsender::RpcError {
168 /// # code: 400, name: "PHONE_CODE_INVALID".to_string(), value: None, caused_by: None });
169 /// #
170 /// match request_result {
171 /// Err(rpc_err) if rpc_err.is("SESSION_PASSWORD_NEEDED") => panic!(),
172 /// Err(rpc_err) if rpc_err.is("PHONE_CODE_*") => {},
173 /// _ => panic!()
174 /// }
175 /// ```
176 pub fn is(&self, rpc_error: &str) -> bool {
177 if let Some(rpc_error) = rpc_error.strip_suffix('*') {
178 self.name.starts_with(rpc_error)
179 } else if let Some(rpc_error) = rpc_error.strip_prefix('*') {
180 self.name.ends_with(rpc_error)
181 } else {
182 self.name == rpc_error
183 }
184 }
185
186 /// Attaches the [`tl::Identifiable::CONSTRUCTOR_ID`] of the
187 /// request that caused this error to the error information.
188 pub fn with_caused_by(mut self, constructor_id: u32) -> Self {
189 self.caused_by = Some(constructor_id);
190 self
191 }
192}
193
194/// This error occurs when a Remote Procedure call was unsuccessful.
195#[derive(Debug)]
196pub enum InvocationError {
197 /// Error propagated from the session.
198 ///
199 /// Most commonly, the session is accessed on each request to query the current
200 /// datacenter, and whenever peers are encountered as those need to be persisted.
201 ///
202 /// The concrete error type is erased as that depends on the session storage used.
203 Session(Box<dyn std::error::Error + Send + Sync>),
204
205 /// The request invocation failed because it was invalid or the server
206 /// could not process it successfully. If the server is suffering from
207 /// temporary issues, the request may be retried after some time.
208 Rpc(RpcError),
209
210 /// Standard I/O error when reading the response.
211 ///
212 /// Telegram may kill the connection at any moment, but it is generally valid to retry
213 /// the request at least once immediately, which will be done through a new connection.
214 Io(io::Error),
215
216 /// Error propagated from attempting to deserialize an invalid [`tl::Deserializable`].
217 ///
218 /// This occurs somewhat frequently when misusing a single session more than once at a time.
219 /// Otherwise it might happen on bleeding-edge layers that have not had time to settle yet.
220 Deserialize(mtp::DeserializeError),
221
222 /// Error propagated from the underlying [`transport`].
223 ///
224 /// The most common variant is [`transport::Error::BadStatus`], which can occur when
225 /// there's no valid Authorization Key (404) or too many connections have been made (429).
226 Transport(transport::Error),
227
228 /// The request was cancelled or dropped, and the results won't arrive.
229 /// This may mean that the [`crate::SenderPoolRunner`] is no longer running.
230 ///
231 /// Higher level APIs may choose to use this variant when a request cannot
232 /// even be sent in the first place (for example, when a peer reference is missing).
233 Dropped,
234
235 /// The request was invoked in a datacenter that does not exist or is not known by the session.
236 InvalidDc,
237
238 /// The request caused the sender to connect to a new datacenter to be performed,
239 /// but the Authorization Key generation process failed.
240 Authentication(authentication::Error),
241}
242
243impl std::error::Error for InvocationError {}
244
245impl fmt::Display for InvocationError {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 match self {
248 Self::Session(_) => write!(f, "session error"),
249 Self::Rpc(err) => write!(f, "request error: {err}"),
250 Self::Io(err) => write!(f, "request error: {err}"),
251 Self::Deserialize(err) => write!(f, "request error: {err}"),
252 Self::Transport(err) => write!(f, "request error: {err}"),
253 Self::Dropped => write!(f, "request error: dropped (cancelled)"),
254 Self::InvalidDc => write!(f, "request error: invalid dc"),
255 Self::Authentication(err) => write!(f, "request error: {err}"),
256 }
257 }
258}
259
260impl From<Box<dyn std::error::Error + Send + Sync>> for InvocationError {
261 fn from(error: Box<dyn std::error::Error + Send + Sync>) -> Self {
262 Self::Session(error)
263 }
264}
265
266impl From<ReadError> for InvocationError {
267 fn from(error: ReadError) -> Self {
268 match error {
269 ReadError::Io(error) => Self::from(error),
270 ReadError::Transport(error) => Self::from(error),
271 ReadError::Deserialize(error) => Self::from(error),
272 }
273 }
274}
275
276impl From<mtp::DeserializeError> for InvocationError {
277 fn from(error: mtp::DeserializeError) -> Self {
278 Self::Deserialize(error)
279 }
280}
281
282impl From<transport::Error> for InvocationError {
283 fn from(error: transport::Error) -> Self {
284 Self::Transport(error)
285 }
286}
287
288impl From<tl::deserialize::Error> for InvocationError {
289 fn from(error: tl::deserialize::Error) -> Self {
290 Self::Deserialize(error.into())
291 }
292}
293
294impl From<io::Error> for InvocationError {
295 fn from(error: io::Error) -> Self {
296 Self::Io(error)
297 }
298}
299
300impl From<authentication::Error> for InvocationError {
301 fn from(error: authentication::Error) -> Self {
302 Self::Authentication(error)
303 }
304}
305
306impl InvocationError {
307 /// Matches on the name of the RPC error (case-sensitive).
308 ///
309 /// Useful in `match` arm guards. A single trailing or leading asterisk (`'*'`) is allowed,
310 /// and will instead check if the error name starts (or ends with) the input parameter.
311 ///
312 /// If the error is not a RPC error, returns `false`.
313 ///
314 /// # Examples
315 ///
316 /// ```
317 /// # let request_result = Result::<(), _>::Err(grammers_mtsender::InvocationError::Rpc(
318 /// # grammers_mtsender::RpcError { code: 400, name: "PHONE_CODE_INVALID".to_string(), value: None, caused_by: None }));
319 /// #
320 /// match request_result {
321 /// Err(err) if err.is("SESSION_PASSWORD_NEEDED") => panic!(),
322 /// Err(err) if err.is("PHONE_CODE_*") => {},
323 /// _ => panic!()
324 /// }
325 /// ```
326 #[inline]
327 pub fn is(&self, rpc_error: &str) -> bool {
328 match self {
329 Self::Rpc(rpc) => rpc.is(rpc_error),
330 _ => false,
331 }
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338
339 #[test]
340 fn check_rpc_error_parsing() {
341 assert_eq!(
342 RpcError::from(tl::types::RpcError {
343 error_code: 400,
344 error_message: "CHAT_INVALID".into(),
345 }),
346 RpcError {
347 code: 400,
348 name: "CHAT_INVALID".into(),
349 value: None,
350 caused_by: None,
351 }
352 );
353 }
354}