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