mastodon_async/
errors.rs

1use std::string::FromUtf8Error;
2use std::{error, fmt, io::Error as IoError, num::TryFromIntError};
3
4use derive_is_enum_variant::is_enum_variant;
5#[cfg(feature = "env")]
6use envy::Error as EnvyError;
7use reqwest::{header::ToStrError as HeaderStrError, Error as HttpError, StatusCode};
8use serde::Deserialize;
9use serde_json::Error as SerdeError;
10use serde_urlencoded::ser::Error as UrlEncodedError;
11#[cfg(feature = "toml")]
12use tomlcrate::de::Error as TomlDeError;
13#[cfg(feature = "toml")]
14use tomlcrate::ser::Error as TomlSerError;
15use url::ParseError as UrlError;
16
17/// Convience type over `std::result::Result` with `Error` as the error type.
18pub type Result<T> = ::std::result::Result<T, Error>;
19
20/// enum of possible errors encountered using the mastodon API.
21#[derive(Debug, thiserror::Error, is_enum_variant)]
22pub enum Error {
23    /// Error from the Mastodon API. This typically means something went
24    /// wrong with your authentication or data.
25    #[error("API error: status: {status:?}, response:\n{response:#?}")]
26    Api {
27        /// The response status.
28        status: StatusCode,
29        /// The JSON-decoded error response from the server.
30        response: ApiError,
31    },
32    /// Error deserialising to json. Typically represents a breaking change in
33    /// the Mastodon API
34    #[error("error from serde")]
35    Serde(#[from] SerdeError),
36    /// Error serializing to url-encoded string
37    #[error("error serializing to url-encoded string")]
38    UrlEncoded(#[from] UrlEncodedError),
39    /// Error encountered in the HTTP backend while requesting a route.
40    #[error("Error encountered in the HTTP backend while requesting a route.")]
41    Http(#[from] HttpError),
42    /// Wrapper around the `std::io::Error` struct.
43    #[error("io error")]
44    Io(#[from] IoError),
45    /// Wrapper around the `url::ParseError` struct.
46    #[error("error parsing URL")]
47    Url(#[from] UrlError),
48    /// Missing Client Id.
49    #[error("Missing Client Id.")]
50    ClientIdRequired,
51    /// Missing Client Secret.
52    #[error("Missing Client Secret.")]
53    ClientSecretRequired,
54    /// Missing Access Token.
55    #[error("Missing Access Token.")]
56    AccessTokenRequired,
57    /// MastodonBuilder & AppBuilder error
58    #[error("builder required field {0:?} to be constructed")]
59    MissingField(&'static str),
60    #[cfg(feature = "toml")]
61    /// Error serializing to toml
62    #[error("Error serializing to toml")]
63    TomlSer(#[from] TomlSerError),
64    #[cfg(feature = "toml")]
65    /// Error deserializing from toml
66    #[error("Error deserializing from toml")]
67    TomlDe(#[from] TomlDeError),
68
69    #[cfg(any(feature = "toml", feature = "json"))]
70    /// Error raised in the helpers::json::to_writer or helpers::toml::to_writer function if not
71    /// all bytes were written to the writer
72    #[error("Not all bytes were written")]
73    NotAllBytesWritten,
74
75    /// Error converting an http header to a string
76    #[error("Error converting an http header to a string")]
77    HeaderStrError(#[from] HeaderStrError),
78    /// Error parsing the http Link header
79    #[error("error parsing http link header")]
80    LinkHeaderParse(#[from] parse_link_header::Error),
81    /// Error returned when an unexpected rel was parsed.
82    #[error("unrecognized rel {rel:?} in link header {link:?}")]
83    UnrecognizedRel {
84        /// The relation which was not recognized
85        rel: String,
86        /// The raw link header
87        link: String,
88    },
89    #[cfg(feature = "env")]
90    /// Error deserializing config from the environment
91    #[error("Error deserializing config from the environment")]
92    Envy(#[from] EnvyError),
93    /// An integer conversion was attempted, but the value didn't fit into the
94    /// target type.
95    ///
96    /// At the time of writing, this can only be triggered when a file is
97    /// larger than the system's usize allows.
98    #[error("integer didn't fit in the target size")]
99    IntConversion(#[from] TryFromIntError),
100    /// Error from mastodon-async-entities
101    #[error(transparent)]
102    Entities(#[from] mastodon_async_entities::error::Error),
103    /// Error parsing UTF-8 string from bytes
104    #[error(transparent)]
105    FromUtf8(#[from] FromUtf8Error),
106    /// Other errors
107    #[error("other error: {0:?}")]
108    Other(String),
109}
110
111/// Error returned from the Mastodon API.
112#[derive(Clone, Debug, Deserialize, Serialize)]
113pub struct ApiError {
114    /// The error message.
115    pub error: String,
116    /// A longer description of the error, mainly provided with the OAuth API.
117    pub error_description: Option<String>,
118}
119
120impl fmt::Display for ApiError {
121    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122        write!(f, "{self:?}")
123    }
124}
125
126impl error::Error for ApiError {}
127
128#[macro_export]
129/// Used to easily create errors from strings
130macro_rules! format_err {
131    ( $( $arg:tt )* ) => {
132        {
133            $crate::Error::Other(format!($($arg)*))
134        }
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use std::io;
142
143    macro_rules! assert_is {
144        ($err:ident, $variant:pat) => {
145            assert!(match $err {
146                $variant => true,
147                _ => false,
148            });
149        };
150    }
151
152    #[tokio::test]
153    async fn from_http_error() {
154        let err: HttpError = reqwest::get("not an actual URL").await.unwrap_err();
155        let err: Error = Error::from(err);
156        assert_is!(err, Error::Http(..));
157    }
158
159    #[test]
160    fn from_io_error() {
161        let err: IoError = io::Error::new(io::ErrorKind::Other, "other error");
162        let err: Error = Error::from(err);
163        assert_is!(err, Error::Io(..));
164    }
165
166    #[test]
167    fn from_serde_error() {
168        let err: SerdeError = serde_json::from_str::<()>("not valid json").unwrap_err();
169        let err: Error = Error::from(err);
170        assert_is!(err, Error::Serde(..));
171    }
172
173    #[test]
174    fn from_url_encoded_error() {
175        let err: UrlEncodedError = serde_urlencoded::ser::Error::Custom("error".into());
176        let err: Error = Error::from(err);
177        assert_is!(err, Error::UrlEncoded(..));
178    }
179
180    #[test]
181    fn from_url_error() {
182        let err: UrlError = UrlError::EmptyHost;
183        let err: Error = Error::from(err);
184        assert_is!(err, Error::Url(..));
185    }
186
187    #[cfg(feature = "toml")]
188    #[test]
189    fn from_toml_de_error() {
190        use tomlcrate;
191        let err: TomlDeError = tomlcrate::from_str::<()>("not valid toml").unwrap_err();
192        let err: Error = Error::from(err);
193        assert_is!(err, Error::TomlDe(..));
194    }
195}