use serde::Deserialize; use std::{error, fmt, io::Error as IoError}; #[cfg(feature = "toml")] use ::toml::de::Error as TomlDeError; #[cfg(feature = "toml")] use ::toml::ser::Error as TomlSerError; #[cfg(feature = "env")] use envy::Error as EnvyError; use hyper_old_types::Error as HeaderParseError; use reqwest::{header::ToStrError as HeaderStrError, Error as HttpError, StatusCode}; use serde_json::Error as SerdeError; use serde_qs::Error as SerdeQsError; use serde_urlencoded::ser::Error as UrlEncodedError; use tungstenite::error::Error as WebSocketError; use url::ParseError as UrlError; /// Convience type over `std::result::Result` with `Error` as the error type. pub type Result = ::std::result::Result; /// enum of possible errors encountered using the mastodon API. #[derive(Debug)] pub enum Error { /// Error from the Mastodon API. This typically means something went /// wrong with your authentication or data. Api(ApiError), /// Error deserialising to json. Typically represents a breaking change in /// the Mastodon API Serde(SerdeError), /// Error serializing to url-encoded string UrlEncoded(UrlEncodedError), /// Error encountered in the HTTP backend while requesting a route. Http(HttpError), /// Wrapper around the `std::io::Error` struct. Io(IoError), /// Wrapper around the `url::ParseError` struct. Url(UrlError), /// Missing Client Id. ClientIdRequired, /// Missing Client Secret. ClientSecretRequired, /// Missing Access Token. AccessTokenRequired, /// Generic client error. Client(StatusCode), /// Generic server error. Server(StatusCode), /// MastodonBuilder & AppBuilder error MissingField(&'static str), #[cfg(feature = "toml")] /// Error serializing to toml TomlSer(TomlSerError), #[cfg(feature = "toml")] /// Error deserializing from toml TomlDe(TomlDeError), /// Error converting an http header to a string HeaderStrError(HeaderStrError), /// Error parsing the http Link header HeaderParseError(HeaderParseError), #[cfg(feature = "env")] /// Error deserializing from the environment Envy(EnvyError), /// Error serializing to a query string SerdeQs(SerdeQsError), /// WebSocket error WebSocket(WebSocketError), /// Other errors Other(String), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { Some(match *self { Error::Api(ref e) => e, Error::Serde(ref e) => e, Error::UrlEncoded(ref e) => e, Error::Http(ref e) => e, Error::Io(ref e) => e, Error::Url(ref e) => e, #[cfg(feature = "toml")] Error::TomlSer(ref e) => e, #[cfg(feature = "toml")] Error::TomlDe(ref e) => e, Error::HeaderStrError(ref e) => e, Error::HeaderParseError(ref e) => e, #[cfg(feature = "env")] Error::Envy(ref e) => e, Error::SerdeQs(ref e) => e, Error::WebSocket(ref e) => e, Error::Client(..) | Error::Server(..) => return None, Error::ClientIdRequired => return None, Error::ClientSecretRequired => return None, Error::AccessTokenRequired => return None, Error::MissingField(_) => return None, Error::Other(..) => return None, }) } } /// Error returned from the Mastodon API. #[derive(Clone, Debug, Deserialize)] pub struct ApiError { /// The type of error. pub error: Option, /// The description of the error. pub error_description: Option, } impl fmt::Display for ApiError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } } impl error::Error for ApiError {} macro_rules! from { ($($(#[$met:meta])* $typ:ident, $variant:ident,)*) => { $( $(#[$met])* impl From<$typ> for Error { fn from(from: $typ) -> Self { use crate::Error::*; $variant(from) } } )* } } from! { HttpError, Http, IoError, Io, SerdeError, Serde, UrlEncodedError, UrlEncoded, UrlError, Url, ApiError, Api, #[cfg(feature = "toml")] TomlSerError, TomlSer, #[cfg(feature = "toml")] TomlDeError, TomlDe, HeaderStrError, HeaderStrError, HeaderParseError, HeaderParseError, #[cfg(feature = "env")] EnvyError, Envy, SerdeQsError, SerdeQs, WebSocketError, WebSocket, String, Other, } #[macro_export] /// Used to easily create errors from strings macro_rules! format_err { ( $( $arg:tt )* ) => { { use elefren::Error; Error::Other(format!($($arg)*)) } } } #[cfg(test)] mod tests { use super::*; use reqwest; use serde_json; use serde_urlencoded; use std::io; macro_rules! assert_is { ($err:ident, $variant:pat) => { assert!(match $err { $variant => true, _ => false, }); }; } #[test] fn from_http_error() { let err: HttpError = reqwest::blocking::get("not an actual URL").unwrap_err(); let err: Error = Error::from(err); assert_is!(err, Error::Http(..)); } #[test] fn from_io_error() { let err: IoError = io::Error::new(io::ErrorKind::Other, "other error"); let err: Error = Error::from(err); assert_is!(err, Error::Io(..)); } #[test] fn from_serde_error() { let err: SerdeError = serde_json::from_str::<()>("not valid json").unwrap_err(); let err: Error = Error::from(err); assert_is!(err, Error::Serde(..)); } #[test] fn from_url_encoded_error() { let err: UrlEncodedError = serde_urlencoded::ser::Error::Custom("error".into()); let err: Error = Error::from(err); assert_is!(err, Error::UrlEncoded(..)); } #[test] fn from_url_error() { let err: UrlError = UrlError::EmptyHost; let err: Error = Error::from(err); assert_is!(err, Error::Url(..)); } #[test] fn from_api_error() { let err: ApiError = ApiError { error: None, error_description: None, }; let err: Error = Error::from(err); assert_is!(err, Error::Api(..)); } #[cfg(feature = "toml")] #[test] fn from_toml_ser_error() { let err: TomlSerError = TomlSerError::DateInvalid; let err: Error = Error::from(err); assert_is!(err, Error::TomlSer(..)); } #[cfg(feature = "toml")] #[test] fn from_toml_de_error() { let err: TomlDeError = ::toml::from_str::<()>("not valid toml").unwrap_err(); let err: Error = Error::from(err); assert_is!(err, Error::TomlDe(..)); } }