Skip to main content

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    /// The request invocation failed because it was invalid or the server
198    /// could not process it successfully. If the server is suffering from
199    /// temporary issues, the request may be retried after some time.
200    Rpc(RpcError),
201
202    /// Standard I/O error when reading the response.
203    ///
204    /// Telegram may kill the connection at any moment, but it is generally valid to retry
205    /// the request at least once immediately, which will be done through a new connection.
206    Io(io::Error),
207
208    /// Error propagated from attempting to deserialize an invalid [`tl::Deserializable`].
209    ///
210    /// This occurs somewhat frequently when misusing a single session more than once at a time.
211    /// Otherwise it might happen on bleeding-edge layers that have not had time to settle yet.
212    Deserialize(mtp::DeserializeError),
213
214    /// Error propagated from the underlying [`transport`].
215    ///
216    /// The most common variant is [`transport::Error::BadStatus`], which can occur when
217    /// there's no valid Authorization Key (404) or too many connections have been made (429).
218    Transport(transport::Error),
219
220    /// The request was cancelled or dropped, and the results won't arrive.
221    /// This may mean that the [`crate::SenderPoolRunner`] is no longer running.
222    ///
223    /// Higher level APIs may choose to use this variant when a request cannot
224    /// even be sent in the first place (for example, when a peer reference is missing).
225    Dropped,
226
227    /// The request was invoked in a datacenter that does not exist or is not known by the session.
228    InvalidDc,
229
230    /// The request caused the sender to connect to a new datacenter to be performed,
231    /// but the Authorization Key generation process failed.
232    Authentication(authentication::Error),
233}
234
235impl std::error::Error for InvocationError {}
236
237impl fmt::Display for InvocationError {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        match self {
240            Self::Rpc(err) => write!(f, "request error: {err}"),
241            Self::Io(err) => write!(f, "request error: {err}"),
242            Self::Deserialize(err) => write!(f, "request error: {err}"),
243            Self::Transport(err) => write!(f, "request error: {err}"),
244            Self::Dropped => write!(f, "request error: dropped (cancelled)"),
245            Self::InvalidDc => write!(f, "request error: invalid dc"),
246            Self::Authentication(err) => write!(f, "request error: {err}"),
247        }
248    }
249}
250
251impl From<ReadError> for InvocationError {
252    fn from(error: ReadError) -> Self {
253        match error {
254            ReadError::Io(error) => Self::from(error),
255            ReadError::Transport(error) => Self::from(error),
256            ReadError::Deserialize(error) => Self::from(error),
257        }
258    }
259}
260
261impl From<mtp::DeserializeError> for InvocationError {
262    fn from(error: mtp::DeserializeError) -> Self {
263        Self::Deserialize(error)
264    }
265}
266
267impl From<transport::Error> for InvocationError {
268    fn from(error: transport::Error) -> Self {
269        Self::Transport(error)
270    }
271}
272
273impl From<tl::deserialize::Error> for InvocationError {
274    fn from(error: tl::deserialize::Error) -> Self {
275        Self::Deserialize(error.into())
276    }
277}
278
279impl From<io::Error> for InvocationError {
280    fn from(error: io::Error) -> Self {
281        Self::Io(error)
282    }
283}
284
285impl From<authentication::Error> for InvocationError {
286    fn from(error: authentication::Error) -> Self {
287        Self::Authentication(error)
288    }
289}
290
291impl InvocationError {
292    /// Matches on the name of the RPC error (case-sensitive).
293    ///
294    /// Useful in `match` arm guards. A single trailing or leading asterisk (`'*'`) is allowed,
295    /// and will instead check if the error name starts (or ends with) the input parameter.
296    ///
297    /// If the error is not a RPC error, returns `false`.
298    ///
299    /// # Examples
300    ///
301    /// ```
302    /// # let request_result = Result::<(), _>::Err(grammers_mtsender::InvocationError::Rpc(
303    /// #     grammers_mtsender::RpcError { code: 400, name: "PHONE_CODE_INVALID".to_string(), value: None, caused_by: None }));
304    /// #
305    /// match request_result {
306    ///     Err(err) if err.is("SESSION_PASSWORD_NEEDED") => panic!(),
307    ///     Err(err) if err.is("PHONE_CODE_*") => {},
308    ///     _ => panic!()
309    /// }
310    /// ```
311    #[inline]
312    pub fn is(&self, rpc_error: &str) -> bool {
313        match self {
314            Self::Rpc(rpc) => rpc.is(rpc_error),
315            _ => false,
316        }
317    }
318}
319
320#[cfg(test)]
321mod tests {
322    use super::*;
323
324    #[test]
325    fn check_rpc_error_parsing() {
326        assert_eq!(
327            RpcError::from(tl::types::RpcError {
328                error_code: 400,
329                error_message: "CHAT_INVALID".into(),
330            }),
331            RpcError {
332                code: 400,
333                name: "CHAT_INVALID".into(),
334                value: None,
335                caused_by: None,
336            }
337        );
338    }
339}