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