automate 0.4.0

An asynchronous library to interact with Discord API and make bots
use std::{fmt, result};
use chrono::{NaiveDateTime, Local, DateTime, Utc};

/// Represents an error that occurred while using the library.
#[derive(Debug)]
pub enum Error {
    Gateway(String),
    Http(String),
    InvalidToken(TokenContext),
    NoPermission(TokenContext),
    RateLimited(RlContext),
    Json(JsonContext),
    Other(String),
}

/// Provides context when an error related to the
/// token is emitted.
#[derive(Debug)]
pub struct TokenContext {
    /// The endpoint on which this error occurred
    pub endpoint: String,
    /// The token used to send the request
    pub token: String,
}

/// Context for errors emitted when the bot is
/// being rate limited.
#[derive(Debug)]
pub struct RlContext {
    /// The endpoint for which the rate-limit has been hit
    pub endpoint: String,
    /// Date when the rate-limit will be removed
    pub until: NaiveDateTime,
    /// Whether this error was generated by the library preventing
    /// you from getting rate-limited or if it was sent by discord
    /// because of a real rate-limit
    pub prevented: bool,
}

/// Errors spawned in the [json](automate::encode::json) module.
#[derive(Debug)]
pub struct JsonContext {
    pub message: String,
    #[cfg(feature = "backtrace")]
    pub backtrace: String,
}

impl Error {
    pub fn new<S>(msg: S) -> Error where S: ToString {
        Error::Other(msg.to_string())
    }

    pub fn err<S, T>(msg: S) -> Result<T, Error> where S: ToString {
        Err(Error::Other(msg.to_string()))
    }

    pub(crate) fn gateway<S, T>(msg: S) -> Result<T, Error> where S: ToString {
        Err(Error::Gateway(msg.to_string()))
    }

    pub(crate) fn http<S, T>(msg: S) -> Result<T, Error> where S: ToString {
        Err(Error::Http(msg.to_string()))
    }

    pub(crate) fn invalid_token<T>(endpoint: &str, token: &str) -> Result<T, Error> {
        Err(Error::InvalidToken(TokenContext {
            endpoint: endpoint.to_owned(),
            token: token.to_owned(),
        }))
    }

    pub(crate) fn no_permission<T>(endpoint: &str, token: &str) -> Result<T, Error> {
        Err(Error::NoPermission(TokenContext {
            endpoint: endpoint.to_owned(),
            token: token.to_owned(),
        }))
    }

    pub(crate) fn rate_limited<T>(endpoint: &str, until: NaiveDateTime, prevented: bool) -> Result<T, Error> {
        Err(Error::RateLimited(RlContext {
            endpoint: endpoint.to_owned(),
            until,
            prevented,
        }))
    }

    pub(crate) fn json<S, T>(message: S) -> Result<T, Error> where S: ToString {
        Err(Error::Json(JsonContext {
            message: message.to_string(),
            #[cfg(feature = "backtrace")]
            backtrace: format!("{:#?}", backtrace::Backtrace::new()),
        }))
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> result::Result<(), fmt::Error> {
        match self {
            Error::Gateway(s) => write!(f, "{}", s),
            Error::Http(s) => write!(f, "{}", s),
            Error::InvalidToken(ctx) => write!(f, "Invalid token `{}`", ctx.token),
            Error::NoPermission(ctx) => write!(f, "Token `{}` does not have the permission to call `{}`", ctx.token, ctx.endpoint),
            Error::RateLimited(ctx) => {
                let datetime: DateTime<Utc> = DateTime::from_utc(ctx.until, Utc);
                let local = datetime.with_timezone(&Local);

                if ctx.prevented {
                    write!(f, "Cancelled to avoid reaching rate limit for endpoint `̀{}` wait until {}", ctx.endpoint, local)
                } else {
                    write!(f, "Reached rate limit for endpoint `̀{}` until {}", ctx.endpoint, local)
                }
            },
            Error::Json(s) =>  {
                #[cfg(feature = "backtrace")] write!(f, "{}\n{}", s.message, s.backtrace)?;
                #[cfg(not(feature = "backtrace"))] write!(f, "{}", s.message)?;

                Ok(())
            },
            Error::Other(s) => write!(f, "{}", s),
        }
    }
}

impl<T: std::error::Error> From<T> for Error {
    fn from(err: T) -> Self {
        Error::new(err.to_string())
    }
}