#![allow(non_upper_case_globals)]
use std::fmt;
pub type Result<T> = std::result::Result<T, BotError>;
pub const CodeNeedReConnect: i32 = 9000;
pub const CodeInvalidSession: i32 = 9001;
pub const CodeURLInvalid: i32 = 9002;
pub const CodeNotFoundOpenAPI: i32 = 9003;
pub const CodeSessionLimit: i32 = 9004;
pub const CodeConnCloseCantResume: i32 = 9005;
pub const CodeConnCloseCantIdentify: i32 = 9006;
pub const CodePagerIsNil: i32 = 9007;
pub const WSCodeBackendUnknownError: u16 = 4000;
pub const WSCodeBackendUnknownOpCode: u16 = 4001;
pub const WSCodeBackendDecodeError: u16 = 4002;
pub const WSCodeBackendNotAuthenticate: u16 = 4003;
pub const WSCodeBackendAuthenticationFail: u16 = 4004;
pub const WSCodeBackendAlreadyAuthenticate: u16 = 4005;
pub const WSCodeBackendSessionNoLongerValid: u16 = 4006;
pub const WSCodeBackendInvalidSeq: u16 = 4007;
pub const WSCodeBackendRateLimit: u16 = 4008;
pub const WSCodeBackendSessionTimeOut: u16 = 4009;
pub const WSCodeBackendInvalidShard: u16 = 4010;
pub const WSCodeBackendShardingRequired: u16 = 4011;
pub const WSCodeBackendInvalidAPIVersion: u16 = 4012;
pub const WSCodeBackendInvalidIntents: u16 = 4013;
pub const WSCodeBackendDisallowdIntents: u16 = 4014;
pub const WSCodeBackendBotOffline: u16 = 4914;
pub const WSCodeBackendBotBanned: u16 = 4915;
pub const APICodeTokenExpireOrNotExist: u32 = 11244;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Err {
code: i32,
text: String,
trace: String,
}
impl Err {
pub fn new(code: i32, text: impl Into<String>, trace: Option<impl Into<String>>) -> Self {
Self {
code,
text: text.into(),
trace: trace.map(Into::into).unwrap_or_default(),
}
}
pub const fn code(&self) -> i32 {
self.code
}
pub fn text(&self) -> &str {
&self.text
}
pub fn trace(&self) -> &str {
&self.trace
}
#[allow(non_snake_case)]
pub const fn Code(&self) -> i32 {
self.code()
}
#[allow(non_snake_case)]
pub fn Text(&self) -> &str {
self.text()
}
#[allow(non_snake_case)]
pub fn Trace(&self) -> &str {
self.trace()
}
}
#[allow(non_snake_case)]
pub fn Code(err: &(dyn std::error::Error + 'static)) -> i32 {
Error(err).Code()
}
impl std::error::Error for Err {}
impl fmt::Display for Err {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"code:{}, text:{}, traceID:{}",
self.code, self.text, self.trace
)
}
}
#[allow(non_snake_case)]
pub fn New(code: i32, text: impl Into<String>) -> Err {
Err::new(code, text, None::<String>)
}
#[allow(non_snake_case)]
pub fn Error(err: &(dyn std::error::Error + 'static)) -> Err {
if let Some(err) = err.downcast_ref::<Err>() {
return err.clone();
}
if let Some(BotError::Sdk(err)) = err.downcast_ref::<BotError>() {
return err.clone();
}
Err::new(9999, err.to_string(), None::<String>)
}
pub fn err_need_reconnect() -> Err {
Err::new(CodeNeedReConnect, "need reconnect", None::<String>)
}
pub fn err_invalid_session() -> Err {
Err::new(CodeConnCloseCantResume, "invalid session", None::<String>)
}
pub fn err_url_invalid() -> Err {
Err::new(
CodeConnCloseCantIdentify,
"ws ap url is invalid",
None::<String>,
)
}
pub fn err_session_limit() -> Err {
Err::new(
CodeConnCloseCantIdentify,
"session num limit",
None::<String>,
)
}
pub fn err_not_found_openapi() -> Err {
Err::new(
CodeNotFoundOpenAPI,
"not found openapi version",
None::<String>,
)
}
pub fn err_pager_is_nil() -> Err {
Err::new(CodePagerIsNil, "pager is nil", None::<String>)
}
pub static ErrNeedReConnect: std::sync::LazyLock<Err> =
std::sync::LazyLock::new(err_need_reconnect);
pub static ErrInvalidSession: std::sync::LazyLock<Err> =
std::sync::LazyLock::new(err_invalid_session);
pub static ErrURLInvalid: std::sync::LazyLock<Err> = std::sync::LazyLock::new(err_url_invalid);
pub static ErrSessionLimit: std::sync::LazyLock<Err> = std::sync::LazyLock::new(err_session_limit);
pub static ErrNotFoundOpenAPI: std::sync::LazyLock<Err> =
std::sync::LazyLock::new(err_not_found_openapi);
pub static ErrPagerIsNil: std::sync::LazyLock<Err> = std::sync::LazyLock::new(err_pager_is_nil);
#[derive(Debug, thiserror::Error)]
pub enum BotError {
#[error("{0}")]
Sdk(#[from] Err),
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("WebSocket error: {0}")]
WebSocket(Box<tokio_tungstenite::tungstenite::Error>),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("URL error: {0}")]
Url(#[from] url::ParseError),
#[error("API error: {code} - {message}")]
Api { code: u32, message: String },
#[error("Authentication failed: {0}")]
AuthenticationFailed(String),
#[error("Not found: {0}")]
NotFound(String),
#[error("Method not allowed: {0}")]
MethodNotAllowed(String),
#[error("Forbidden: {0}")]
Forbidden(String),
#[error("Sequence number error: {0}")]
SequenceNumber(String),
#[error("Server error: {0}")]
Server(String),
#[error("Authentication error: {0}")]
Auth(String),
#[error("Connection error: {0}")]
Connection(String),
#[error("Rate limited: retry after {retry_after} seconds")]
RateLimit { retry_after: u64 },
#[error("Invalid configuration: {0}")]
Config(String),
#[error("Invalid data: {0}")]
InvalidData(String),
#[error("Network timeout")]
Timeout,
#[error("Gateway error: {0}")]
Gateway(String),
#[error("Session error: {0}")]
Session(String),
#[error("Internal error: {0}")]
Internal(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Not implemented: {0}")]
NotImplemented(String),
}
impl BotError {
pub fn api(code: u32, message: impl Into<String>) -> Self {
Self::Api {
code,
message: message.into(),
}
}
pub fn auth(message: impl Into<String>) -> Self {
Self::Auth(message.into())
}
pub fn connection(message: impl Into<String>) -> Self {
Self::Connection(message.into())
}
pub fn config(message: impl Into<String>) -> Self {
Self::Config(message.into())
}
pub fn invalid_data(message: impl Into<String>) -> Self {
Self::InvalidData(message.into())
}
pub fn gateway(message: impl Into<String>) -> Self {
Self::Gateway(message.into())
}
pub fn session(message: impl Into<String>) -> Self {
Self::Session(message.into())
}
pub fn internal(message: impl Into<String>) -> Self {
Self::Internal(message.into())
}
pub fn rate_limit(retry_after: u64) -> Self {
Self::RateLimit { retry_after }
}
pub fn not_implemented(message: impl Into<String>) -> Self {
Self::NotImplemented(message.into())
}
pub fn is_retryable(&self) -> bool {
match self {
BotError::Http(e) => e.is_timeout() || e.is_connect(),
BotError::WebSocket(_) => true,
BotError::Connection(_) => true,
BotError::Timeout => true,
BotError::Gateway(_) => true,
BotError::RateLimit { .. } => true,
_ => false,
}
}
pub fn retry_after(&self) -> Option<u64> {
match self {
BotError::RateLimit { retry_after } => Some(*retry_after),
BotError::Connection(_) => Some(5),
BotError::Gateway(_) => Some(1),
BotError::Timeout => Some(3),
_ if self.is_retryable() => Some(1),
_ => None,
}
}
}
pub trait IntoBotError<T> {
fn with_context(self, context: &str) -> Result<T>;
}
impl<T, E> IntoBotError<T> for std::result::Result<T, E>
where
E: fmt::Display,
{
fn with_context(self, context: &str) -> Result<T> {
self.map_err(|e| BotError::internal(format!("{context}: {e}")))
}
}
impl From<tokio_tungstenite::tungstenite::Error> for BotError {
fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
BotError::WebSocket(Box::new(err))
}
}
pub fn http_error_from_status(status: u16, message: String) -> BotError {
match status {
401 => BotError::AuthenticationFailed(message),
403 => BotError::Forbidden(message),
404 => BotError::NotFound(message),
405 => BotError::MethodNotAllowed(message),
429 => BotError::SequenceNumber(message),
500 | 504 => BotError::Server(message),
_ => BotError::api(status as u32, message),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_botgo_err_helpers() {
let err = New(CodeNeedReConnect, "need reconnect");
assert_eq!(err.Code(), CodeNeedReConnect);
assert_eq!(err.Text(), "need reconnect");
assert_eq!(err.Trace(), "");
assert_eq!(err.to_string(), "code:9000, text:need reconnect, traceID:");
assert_eq!(err_pager_is_nil().Code(), CodePagerIsNil);
assert_eq!(err_invalid_session().Code(), CodeConnCloseCantResume);
}
}