twilight_http/
error.rs

1use crate::{api_error::ApiError, json::JsonError, response::StatusCode};
2use std::{
3    error::Error as StdError,
4    fmt::{Debug, Display, Formatter, Result as FmtResult},
5    str,
6};
7
8#[derive(Debug)]
9pub struct Error {
10    pub(super) kind: ErrorType,
11    pub(super) source: Option<Box<dyn StdError + Send + Sync>>,
12}
13
14impl Error {
15    /// Immutable reference to the type of error that occurred.
16    #[must_use = "retrieving the type has no effect if left unused"]
17    pub const fn kind(&self) -> &ErrorType {
18        &self.kind
19    }
20
21    /// Consume the error, returning the source error if there is any.
22    #[must_use = "consuming the error and retrieving the source has no effect if left unused"]
23    pub fn into_source(self) -> Option<Box<dyn StdError + Send + Sync>> {
24        self.source
25    }
26
27    /// Consume the error, returning the owned error type and the source error.
28    #[must_use = "consuming the error into its parts has no effect if left unused"]
29    pub fn into_parts(self) -> (ErrorType, Option<Box<dyn StdError + Send + Sync>>) {
30        (self.kind, self.source)
31    }
32
33    pub(super) fn json(source: JsonError) -> Self {
34        Self {
35            kind: ErrorType::Json,
36            source: Some(Box::new(source)),
37        }
38    }
39
40    pub(super) fn validation(source: impl StdError + Send + Sync + 'static) -> Self {
41        Self {
42            kind: ErrorType::Validation,
43            source: Some(Box::new(source)),
44        }
45    }
46}
47
48impl Display for Error {
49    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
50        match &self.kind {
51            ErrorType::BuildingRequest => f.write_str("failed to build the request"),
52            ErrorType::CreatingHeader { name, .. } => {
53                f.write_str("parsing the value for header ")?;
54                f.write_str(name)?;
55
56                f.write_str(" failed")
57            }
58            ErrorType::Json => f.write_str("given value couldn't be serialized"),
59            ErrorType::Parsing { body, .. } => {
60                f.write_str("response body couldn't be deserialized: ")?;
61
62                if let Ok(body) = str::from_utf8(body) {
63                    f.write_str(body)
64                } else {
65                    Debug::fmt(body, f)
66                }
67            }
68            ErrorType::RequestCanceled => {
69                f.write_str("request was canceled either before or while being sent")
70            }
71            ErrorType::RequestError => f.write_str("parsing or receiving the response failed"),
72            ErrorType::RequestTimedOut => f.write_str("request timed out"),
73            ErrorType::Response { body, status, .. } => {
74                f.write_str("response error: status code ")?;
75                Display::fmt(status, f)?;
76                f.write_str(", error: ")?;
77
78                f.write_str(&String::from_utf8_lossy(body))
79            }
80            ErrorType::Unauthorized => {
81                f.write_str("token in use is invalid, expired, or is revoked")
82            }
83            ErrorType::Validation => f.write_str("request fields have invalid values"),
84        }
85    }
86}
87
88impl StdError for Error {
89    fn source(&self) -> Option<&(dyn StdError + 'static)> {
90        self.source
91            .as_ref()
92            .map(|source| &**source as &(dyn StdError + 'static))
93    }
94}
95
96/// Type of [`Error`] that occurred.
97#[non_exhaustive]
98pub enum ErrorType {
99    BuildingRequest,
100    CreatingHeader {
101        name: String,
102    },
103    Json,
104    Parsing {
105        body: Vec<u8>,
106    },
107    RequestCanceled,
108    RequestError,
109    RequestTimedOut,
110    Response {
111        body: Vec<u8>,
112        error: ApiError,
113        status: StatusCode,
114    },
115    /// Token in use has become revoked or is otherwise invalid.
116    ///
117    /// This can occur if a bot token is invalidated or an access token expires
118    /// or is revoked. Recreate the client to configure a new token.
119    Unauthorized,
120    /// A field failed validation requirements during request building.
121    ///
122    /// The inputs of request methods for fields are validated for correctness.
123    /// For example, [`CreateMessage::content`] is validated to ensure that the
124    /// message content isn't too long; [`ExecuteWebhook::embeds`] is validated
125    /// to ensure that a correct number of embeds are provided; and so on.
126    ///
127    /// Validation failures aren't immediately returned; rather, validation
128    /// errors are returned when calling the [`IntoFuture`] or
129    /// [`TryIntoRequest`] implementations on requests.
130    ///
131    /// # Examples
132    ///
133    /// Passing a message with valid content succeeds as expected:
134    ///
135    /// ```no_run
136    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
137    /// # let channel_id = twilight_model::id::Id::new(1);
138    /// use std::env;
139    /// use twilight_http::{client::Client, request::TryIntoRequest};
140    ///
141    /// let client = Client::new(env::var("DISCORD_TOKEN")?);
142    /// let builder = client.create_message(channel_id).content("Ping!");
143    ///
144    /// assert!(builder.try_into_request().is_ok());
145    /// # Ok(()) }
146    /// ```
147    ///
148    /// However, passing an invalid content returns a validation error upon
149    /// finalizing the request building:
150    ///
151    /// ```no_run
152    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
153    /// # let channel_id = twilight_model::id::Id::new(1);
154    /// use std::{env, error::Error};
155    /// use twilight_http::{client::Client, error::ErrorType, request::TryIntoRequest};
156    ///
157    /// let client = Client::new(env::var("DISCORD_TOKEN")?);
158    ///
159    /// // this is a very long message
160    /// let content = "pinkie pie is cool ".repeat(1000);
161    ///
162    /// let builder = client.create_message(channel_id).content(&content);
163    ///
164    /// let error = builder.try_into_request().unwrap_err();
165    /// assert!(matches!(error.kind(), ErrorType::Validation));
166    ///
167    /// // print the contents of the validation error
168    /// println!("{:?}", error.source());
169    /// # Ok(()) }
170    /// ```
171    ///
172    /// [`CreateMessage::content`]: crate::request::channel::message::create_message::CreateMessage
173    /// [`ExecuteWebhook::embeds`]: crate::request::channel::webhook::execute_webhook::ExecuteWebhook
174    /// [`IntoFuture`]: std::future::IntoFuture
175    /// [`TryIntoRequest`]: crate::request::TryIntoRequest
176    Validation,
177}
178
179impl Debug for ErrorType {
180    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
181        match self {
182            Self::BuildingRequest => f.write_str("BuildingRequest"),
183            Self::CreatingHeader { name } => f
184                .debug_struct("CreatingHeader")
185                .field("name", name)
186                .finish(),
187            Self::Json => f.write_str("Json"),
188            Self::Parsing { body } => {
189                let mut debug = f.debug_struct("Parsing");
190
191                if let Ok(body_string) = str::from_utf8(body) {
192                    debug.field("body_string", &body_string);
193                }
194
195                debug.field("body", body).finish()
196            }
197            Self::RequestCanceled => f.write_str("RequestCanceled"),
198            Self::RequestError => f.write_str("RequestError"),
199            Self::RequestTimedOut => f.write_str("RequestTimedOut"),
200            Self::Response {
201                body,
202                error,
203                status,
204            } => {
205                let mut debug = f.debug_struct("Response");
206
207                if let Ok(body_string) = str::from_utf8(body) {
208                    debug.field("body_string", &body_string);
209                }
210
211                debug
212                    .field("body", body)
213                    .field("error", error)
214                    .field("status", status)
215                    .finish()
216            }
217            Self::Unauthorized => f.write_str("Unauthorized"),
218            Self::Validation => f.write_str("Validation"),
219        }
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::ErrorType;
226    use crate::{
227        api_error::{ApiError, GeneralApiError},
228        response::StatusCode,
229    };
230
231    /// Ensure
232    #[test]
233    fn parsing_variant_debug() {
234        let body = br#"{"message": "aaa"#.to_vec();
235
236        let error = ErrorType::Parsing { body };
237
238        assert_eq!(
239            "Parsing {
240    body_string: \"{\\\"message\\\": \\\"aaa\",
241    body: [
242        123,
243        34,
244        109,
245        101,
246        115,
247        115,
248        97,
249        103,
250        101,
251        34,
252        58,
253        32,
254        34,
255        97,
256        97,
257        97,
258    ],
259}",
260            format!("{error:#?}"),
261        );
262    }
263
264    #[test]
265    fn response_variant_debug() {
266        let body = br#"{"message": "aaa"}"#.to_vec();
267
268        let error = ErrorType::Response {
269            body,
270            error: ApiError::General(GeneralApiError {
271                code: 0,
272                message: "401: Unauthorized".to_owned(),
273            }),
274            status: StatusCode::new(401),
275        };
276
277        assert_eq!(
278            "Response {
279    body_string: \"{\\\"message\\\": \\\"aaa\\\"}\",
280    body: [
281        123,
282        34,
283        109,
284        101,
285        115,
286        115,
287        97,
288        103,
289        101,
290        34,
291        58,
292        32,
293        34,
294        97,
295        97,
296        97,
297        34,
298        125,
299    ],
300    error: General(
301        GeneralApiError {
302            code: 0,
303            message: \"401: Unauthorized\",
304        },
305    ),
306    status: StatusCode(
307        401,
308    ),
309}",
310            format!("{error:#?}"),
311        );
312    }
313}