1use std::{fmt, io};
6
7#[derive(Clone, Debug, PartialEq)]
16pub struct RpcError {
17 pub code: i32,
19 pub name: String,
21 pub value: Option<u32>,
23}
24
25impl fmt::Display for RpcError {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(f, "RPC {}: {}", self.code, self.name)?;
28 if let Some(v) = self.value {
29 write!(f, " (value: {v})")?;
30 }
31 Ok(())
32 }
33}
34
35impl std::error::Error for RpcError {}
36
37impl RpcError {
38 pub fn from_telegram(code: i32, message: &str) -> Self {
40 if let Some(idx) = message.rfind('_') {
43 let suffix = &message[idx + 1..];
44 if !suffix.is_empty() && suffix.chars().all(|c| c.is_ascii_digit()) {
45 if let Ok(v) = suffix.parse::<u32>() {
46 let name = message[..idx].to_string();
47 return Self { code, name, value: Some(v) };
48 }
49 }
50 }
51 Self { code, name: message.to_string(), value: None }
52 }
53
54 pub fn is(&self, pattern: &str) -> bool {
61 if let Some(prefix) = pattern.strip_suffix('*') {
62 self.name.starts_with(prefix)
63 } else if let Some(suffix) = pattern.strip_prefix('*') {
64 self.name.ends_with(suffix)
65 } else {
66 self.name == pattern
67 }
68 }
69
70 pub fn flood_wait_seconds(&self) -> Option<u64> {
72 if self.code == 420 && self.name == "FLOOD_WAIT" {
73 self.value.map(|v| v as u64)
74 } else {
75 None
76 }
77 }
78}
79
80#[derive(Debug)]
84pub enum InvocationError {
85 Rpc(RpcError),
87 Io(io::Error),
89 Deserialize(String),
91 Dropped,
93 Migrate(i32),
95}
96
97impl fmt::Display for InvocationError {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 Self::Rpc(e) => write!(f, "{e}"),
101 Self::Io(e) => write!(f, "I/O error: {e}"),
102 Self::Deserialize(s) => write!(f, "deserialize error: {s}"),
103 Self::Dropped => write!(f, "request dropped"),
104 Self::Migrate(dc) => write!(f, "DC migration to {dc}"),
105 }
106 }
107}
108
109impl std::error::Error for InvocationError {}
110
111impl From<io::Error> for InvocationError {
112 fn from(e: io::Error) -> Self { Self::Io(e) }
113}
114
115impl From<layer_tl_types::deserialize::Error> for InvocationError {
116 fn from(e: layer_tl_types::deserialize::Error) -> Self { Self::Deserialize(e.to_string()) }
117}
118
119impl InvocationError {
120 pub fn is(&self, pattern: &str) -> bool {
122 match self {
123 Self::Rpc(e) => e.is(pattern),
124 _ => false,
125 }
126 }
127
128 pub fn flood_wait_seconds(&self) -> Option<u64> {
130 match self {
131 Self::Rpc(e) => e.flood_wait_seconds(),
132 _ => None,
133 }
134 }
135}
136
137#[derive(Debug)]
141pub enum SignInError {
142 SignUpRequired,
144 PasswordRequired(PasswordToken),
146 InvalidCode,
148 Other(InvocationError),
150}
151
152impl fmt::Display for SignInError {
153 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
154 match self {
155 Self::SignUpRequired => write!(f, "sign up required — use official Telegram app"),
156 Self::PasswordRequired(_) => write!(f, "2FA password required"),
157 Self::InvalidCode => write!(f, "invalid or expired code"),
158 Self::Other(e) => write!(f, "{e}"),
159 }
160 }
161}
162
163impl std::error::Error for SignInError {}
164
165impl From<InvocationError> for SignInError {
166 fn from(e: InvocationError) -> Self { Self::Other(e) }
167}
168
169pub struct PasswordToken {
175 pub(crate) password: layer_tl_types::types::account::Password,
176}
177
178impl PasswordToken {
179 pub fn hint(&self) -> Option<&str> {
181 self.password.hint.as_deref()
182 }
183}
184
185impl fmt::Debug for PasswordToken {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 write!(f, "PasswordToken {{ hint: {:?} }}", self.hint())
188 }
189}
190
191pub struct LoginToken {
197 pub(crate) phone: String,
198 pub(crate) phone_code_hash: String,
199}