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
17pub type Result<T> = ::std::result::Result<T, Error>;
19
20#[derive(Debug, thiserror::Error, is_enum_variant)]
22pub enum Error {
23 #[error("API error: status: {status:?}, response:\n{response:#?}")]
26 Api {
27 status: StatusCode,
29 response: ApiError,
31 },
32 #[error("error from serde")]
35 Serde(#[from] SerdeError),
36 #[error("error serializing to url-encoded string")]
38 UrlEncoded(#[from] UrlEncodedError),
39 #[error("Error encountered in the HTTP backend while requesting a route.")]
41 Http(#[from] HttpError),
42 #[error("io error")]
44 Io(#[from] IoError),
45 #[error("error parsing URL")]
47 Url(#[from] UrlError),
48 #[error("Missing Client Id.")]
50 ClientIdRequired,
51 #[error("Missing Client Secret.")]
53 ClientSecretRequired,
54 #[error("Missing Access Token.")]
56 AccessTokenRequired,
57 #[error("builder required field {0:?} to be constructed")]
59 MissingField(&'static str),
60 #[cfg(feature = "toml")]
61 #[error("Error serializing to toml")]
63 TomlSer(#[from] TomlSerError),
64 #[cfg(feature = "toml")]
65 #[error("Error deserializing from toml")]
67 TomlDe(#[from] TomlDeError),
68
69 #[cfg(any(feature = "toml", feature = "json"))]
70 #[error("Not all bytes were written")]
73 NotAllBytesWritten,
74
75 #[error("Error converting an http header to a string")]
77 HeaderStrError(#[from] HeaderStrError),
78 #[error("error parsing http link header")]
80 LinkHeaderParse(#[from] parse_link_header::Error),
81 #[error("unrecognized rel {rel:?} in link header {link:?}")]
83 UnrecognizedRel {
84 rel: String,
86 link: String,
88 },
89 #[cfg(feature = "env")]
90 #[error("Error deserializing config from the environment")]
92 Envy(#[from] EnvyError),
93 #[error("integer didn't fit in the target size")]
99 IntConversion(#[from] TryFromIntError),
100 #[error(transparent)]
102 Entities(#[from] mastodon_async_entities::error::Error),
103 #[error(transparent)]
105 FromUtf8(#[from] FromUtf8Error),
106 #[error("other error: {0:?}")]
108 Other(String),
109}
110
111#[derive(Clone, Debug, Deserialize, Serialize)]
113pub struct ApiError {
114 pub error: String,
116 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]
129macro_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}