ferogram_mtsender/
errors.rs1use std::{fmt, io};
14
15#[derive(Clone, Debug, PartialEq)]
22pub struct RpcError {
23 pub code: i32,
25 pub name: String,
27 pub value: Option<u32>,
29}
30
31impl fmt::Display for RpcError {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 write!(f, "RPC {}: {}", self.code, self.name)?;
34 if let Some(v) = self.value {
35 write!(f, " (value: {v})")?;
36 }
37 Ok(())
38 }
39}
40
41impl std::error::Error for RpcError {}
42
43impl RpcError {
44 pub fn from_telegram(code: i32, message: &str) -> Self {
46 if let Some(idx) = message.rfind('_') {
47 let suffix = &message[idx + 1..];
48 if !suffix.is_empty()
49 && suffix.chars().all(|c| c.is_ascii_digit())
50 && let Ok(v) = suffix.parse::<u32>()
51 {
52 let name = message[..idx].to_string();
53 return Self {
54 code,
55 name,
56 value: Some(v),
57 };
58 }
59 }
60 Self {
61 code,
62 name: message.to_string(),
63 value: None,
64 }
65 }
66
67 pub fn is(&self, pattern: &str) -> bool {
69 if let Some(prefix) = pattern.strip_suffix('*') {
70 self.name.starts_with(prefix)
71 } else if let Some(suffix) = pattern.strip_prefix('*') {
72 self.name.ends_with(suffix)
73 } else {
74 self.name == pattern
75 }
76 }
77
78 pub fn flood_wait_seconds(&self) -> Option<u64> {
80 if self.code == 420 && self.name == "FLOOD_WAIT" {
81 self.value.map(|v| v as u64)
82 } else {
83 None
84 }
85 }
86
87 pub fn migrate_dc_id(&self) -> Option<i32> {
89 if self.code != 303 {
90 return None;
91 }
92 let is_migrate = self.name == "PHONE_MIGRATE"
93 || self.name == "NETWORK_MIGRATE"
94 || self.name == "FILE_MIGRATE"
95 || self.name == "USER_MIGRATE"
96 || self.name.ends_with("_MIGRATE");
97 if is_migrate {
98 Some(self.value.unwrap_or(2) as i32)
99 } else {
100 None
101 }
102 }
103}
104
105#[derive(Debug)]
107#[non_exhaustive]
108pub enum InvocationError {
109 Rpc(RpcError),
111 Io(io::Error),
113 Deserialize(String),
115 Dropped,
117 #[doc(hidden)]
119 Migrate(i32),
120 StaleHash,
122 PeerNotCached(String),
124}
125
126impl fmt::Display for InvocationError {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 match self {
129 Self::Rpc(e) => write!(f, "{e}"),
130 Self::Io(e) => write!(f, "I/O error: {e}"),
131 Self::Deserialize(s) => write!(f, "deserialize error: {s}"),
132 Self::Dropped => write!(f, "request dropped"),
133 Self::Migrate(dc) => write!(f, "DC migration to {dc}"),
134 Self::StaleHash => write!(f, "stale access hash; peer cache cleared, retry"),
135 Self::PeerNotCached(s) => write!(f, "peer not cached: {s}"),
136 }
137 }
138}
139
140impl std::error::Error for InvocationError {}
141
142impl From<io::Error> for InvocationError {
143 fn from(e: io::Error) -> Self {
144 Self::Io(e)
145 }
146}
147
148impl From<ferogram_tl_types::deserialize::Error> for InvocationError {
149 fn from(e: ferogram_tl_types::deserialize::Error) -> Self {
150 Self::Deserialize(e.to_string())
151 }
152}
153
154impl From<ferogram_connect::ConnectError> for InvocationError {
155 fn from(e: ferogram_connect::ConnectError) -> Self {
156 use ferogram_connect::ConnectError;
157 match e {
158 ConnectError::Io(e) => Self::Io(e),
159 ConnectError::Other(s) => Self::Deserialize(s),
160 ConnectError::TransportCode(code) => {
161 Self::Rpc(RpcError::from_telegram(code, "transport error"))
162 }
163 ConnectError::Rpc { code, message } => {
164 Self::Rpc(RpcError::from_telegram(code, &message))
165 }
166 }
167 }
168}
169
170impl InvocationError {
171 pub fn is(&self, pattern: &str) -> bool {
173 match self {
174 Self::Rpc(e) => e.is(pattern),
175 _ => false,
176 }
177 }
178
179 pub fn flood_wait_seconds(&self) -> Option<u64> {
181 match self {
182 Self::Rpc(e) => e.flood_wait_seconds(),
183 _ => None,
184 }
185 }
186
187 pub fn migrate_dc_id(&self) -> Option<i32> {
189 match self {
190 Self::Rpc(r) => r.migrate_dc_id(),
191 _ => None,
192 }
193 }
194}