use std::{fmt, io};
#[derive(Clone, Debug, PartialEq)]
pub struct RpcError {
pub code: i32,
pub name: String,
pub value: Option<u32>,
}
impl fmt::Display for RpcError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "RPC {}: {}", self.code, self.name)?;
if let Some(v) = self.value {
write!(f, " (value: {v})")?;
}
Ok(())
}
}
impl std::error::Error for RpcError {}
impl RpcError {
pub fn from_telegram(code: i32, message: &str) -> Self {
if let Some(idx) = message.rfind('_') {
let suffix = &message[idx + 1..];
if !suffix.is_empty()
&& suffix.chars().all(|c| c.is_ascii_digit())
&& let Ok(v) = suffix.parse::<u32>()
{
let name = message[..idx].to_string();
return Self {
code,
name,
value: Some(v),
};
}
}
Self {
code,
name: message.to_string(),
value: None,
}
}
pub fn is(&self, pattern: &str) -> bool {
if let Some(prefix) = pattern.strip_suffix('*') {
self.name.starts_with(prefix)
} else if let Some(suffix) = pattern.strip_prefix('*') {
self.name.ends_with(suffix)
} else {
self.name == pattern
}
}
pub fn flood_wait_seconds(&self) -> Option<u64> {
if self.code == 420 && self.name == "FLOOD_WAIT" {
self.value.map(|v| v as u64)
} else {
None
}
}
pub fn migrate_dc_id(&self) -> Option<i32> {
if self.code != 303 {
return None;
}
let is_migrate = self.name == "PHONE_MIGRATE"
|| self.name == "NETWORK_MIGRATE"
|| self.name == "FILE_MIGRATE"
|| self.name == "USER_MIGRATE"
|| self.name.ends_with("_MIGRATE");
if is_migrate {
Some(self.value.unwrap_or(2) as i32)
} else {
None
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum InvocationError {
Rpc(RpcError),
Io(io::Error),
Deserialize(String),
Dropped,
#[doc(hidden)]
Migrate(i32),
StaleHash,
PeerNotCached(String),
}
impl fmt::Display for InvocationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Rpc(e) => write!(f, "{e}"),
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::Deserialize(s) => write!(f, "deserialize error: {s}"),
Self::Dropped => write!(f, "request dropped"),
Self::Migrate(dc) => write!(f, "DC migration to {dc}"),
Self::StaleHash => write!(f, "stale access hash; peer cache cleared, retry"),
Self::PeerNotCached(s) => write!(f, "peer not cached: {s}"),
}
}
}
impl std::error::Error for InvocationError {}
impl From<io::Error> for InvocationError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
impl From<ferogram_tl_types::deserialize::Error> for InvocationError {
fn from(e: ferogram_tl_types::deserialize::Error) -> Self {
Self::Deserialize(e.to_string())
}
}
impl From<ferogram_connect::ConnectError> for InvocationError {
fn from(e: ferogram_connect::ConnectError) -> Self {
use ferogram_connect::ConnectError;
match e {
ConnectError::Io(e) => Self::Io(e),
ConnectError::Other(s) => Self::Deserialize(s),
ConnectError::TransportCode(code) => {
Self::Rpc(RpcError::from_telegram(code, "transport error"))
}
ConnectError::Rpc { code, message } => {
Self::Rpc(RpcError::from_telegram(code, &message))
}
}
}
}
impl InvocationError {
pub fn is(&self, pattern: &str) -> bool {
match self {
Self::Rpc(e) => e.is(pattern),
_ => false,
}
}
pub fn flood_wait_seconds(&self) -> Option<u64> {
match self {
Self::Rpc(e) => e.flood_wait_seconds(),
_ => None,
}
}
pub fn migrate_dc_id(&self) -> Option<i32> {
match self {
Self::Rpc(r) => r.migrate_dc_id(),
_ => None,
}
}
}