twilight_http/
api_error.rs

1use serde::{Deserialize, Serialize};
2use std::fmt::{Display, Formatter, Result as FmtResult};
3
4#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
5#[non_exhaustive]
6#[serde(untagged)]
7pub enum ApiError {
8    General(GeneralApiError),
9    /// Request has been ratelimited.
10    Ratelimited(RatelimitedApiError),
11    /// Something was wrong with the input when sending a message.
12    Message(MessageApiError),
13}
14
15impl Display for ApiError {
16    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
17        match self {
18            Self::General(inner) => Display::fmt(inner, f),
19            Self::Message(inner) => Display::fmt(inner, f),
20            Self::Ratelimited(inner) => Display::fmt(inner, f),
21        }
22    }
23}
24
25#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
26#[non_exhaustive]
27pub struct GeneralApiError {
28    pub code: u64,
29    pub message: String,
30}
31
32impl Display for GeneralApiError {
33    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
34        f.write_str("Error code ")?;
35        Display::fmt(&self.code, f)?;
36        f.write_str(": ")?;
37
38        f.write_str(&self.message)
39    }
40}
41
42/// Sending a message failed because the provided fields contained invalid
43/// input.
44#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
45#[non_exhaustive]
46pub struct MessageApiError {
47    /// Fields within a provided embed were invalid.
48    pub embed: Option<Vec<MessageApiErrorEmbedField>>,
49}
50
51impl Display for MessageApiError {
52    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
53        f.write_str("message fields invalid: ")?;
54
55        if let Some(embed) = &self.embed {
56            f.write_str("embed (")?;
57
58            let field_count = embed.len().saturating_sub(1);
59
60            for (idx, field) in embed.iter().enumerate() {
61                Display::fmt(field, f)?;
62
63                if idx == field_count {
64                    f.write_str(", ")?;
65                }
66            }
67
68            f.write_str(")")?;
69        }
70
71        Ok(())
72    }
73}
74
75/// Field within a [`MessageApiError`] [embed] list.
76///
77/// [embed]: MessageApiError::embed
78#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
79#[non_exhaustive]
80#[serde(rename_all = "snake_case")]
81pub enum MessageApiErrorEmbedField {
82    /// Something was wrong with the provided fields.
83    Fields,
84    /// The provided timestamp wasn't a valid RFC3339 string.
85    Timestamp,
86}
87
88impl Display for MessageApiErrorEmbedField {
89    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
90        f.write_str(match self {
91            Self::Fields => "fields",
92            Self::Timestamp => "timestamp",
93        })
94    }
95}
96
97#[derive(Clone, Debug, Deserialize, Serialize)]
98#[non_exhaustive]
99pub struct RatelimitedApiError {
100    /// Whether the ratelimit is a global ratelimit.
101    pub global: bool,
102    /// Human readable message provided by the API.
103    pub message: String,
104    /// Amount of time to wait before retrying.
105    pub retry_after: f64,
106}
107
108impl Display for RatelimitedApiError {
109    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
110        f.write_str("Got ")?;
111
112        if self.global {
113            f.write_str("global ")?;
114        }
115
116        f.write_str("ratelimited for ")?;
117        Display::fmt(&self.retry_after, f)?;
118
119        f.write_str("s")
120    }
121}
122
123impl Eq for RatelimitedApiError {}
124
125impl PartialEq for RatelimitedApiError {
126    fn eq(&self, other: &Self) -> bool {
127        self.global == other.global && self.message == other.message
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::{
134        ApiError, GeneralApiError, MessageApiError, MessageApiErrorEmbedField, RatelimitedApiError,
135    };
136    use serde_test::Token;
137
138    #[test]
139    fn api_error_deser() {
140        let expected = GeneralApiError {
141            code: 10001,
142            message: "Unknown account".to_owned(),
143        };
144
145        serde_test::assert_tokens(
146            &expected,
147            &[
148                Token::Struct {
149                    name: "GeneralApiError",
150                    len: 2,
151                },
152                Token::Str("code"),
153                Token::U64(10001),
154                Token::Str("message"),
155                Token::Str("Unknown account"),
156                Token::StructEnd,
157            ],
158        );
159    }
160
161    #[test]
162    fn api_error_message() {
163        let expected = ApiError::Message(MessageApiError {
164            embed: Some(
165                [
166                    MessageApiErrorEmbedField::Fields,
167                    MessageApiErrorEmbedField::Timestamp,
168                ]
169                .to_vec(),
170            ),
171        });
172
173        serde_test::assert_tokens(
174            &expected,
175            &[
176                Token::Struct {
177                    name: "MessageApiError",
178                    len: 1,
179                },
180                Token::Str("embed"),
181                Token::Some,
182                Token::Seq { len: Some(2) },
183                Token::UnitVariant {
184                    name: "MessageApiErrorEmbedField",
185                    variant: "fields",
186                },
187                Token::UnitVariant {
188                    name: "MessageApiErrorEmbedField",
189                    variant: "timestamp",
190                },
191                Token::SeqEnd,
192                Token::StructEnd,
193            ],
194        );
195    }
196
197    #[test]
198    fn ratelimited_api_error() {
199        let expected = RatelimitedApiError {
200            global: true,
201            message: "You are being rate limited.".to_owned(),
202            retry_after: 6.457,
203        };
204
205        serde_test::assert_tokens(
206            &expected,
207            &[
208                Token::Struct {
209                    name: "RatelimitedApiError",
210                    len: 3,
211                },
212                Token::Str("global"),
213                Token::Bool(true),
214                Token::Str("message"),
215                Token::Str("You are being rate limited."),
216                Token::Str("retry_after"),
217                Token::F64(6.457),
218                Token::StructEnd,
219            ],
220        );
221    }
222
223    /// Assert that deserializing an [`ApiError::Ratelimited`] variant uses
224    /// the correct variant.
225    ///
226    /// Tests for [#1302], which was due to a previously ordered variant having
227    /// higher priority for untagged deserialization.
228    ///
229    /// [#1302]: https://github.com/twilight-rs/twilight/issues/1302
230    #[test]
231    fn api_error_variant_ratelimited() {
232        let expected = ApiError::Ratelimited(RatelimitedApiError {
233            global: false,
234            message: "You are being rate limited.".to_owned(),
235            retry_after: 0.362,
236        });
237
238        serde_test::assert_tokens(
239            &expected,
240            &[
241                Token::Struct {
242                    name: "RatelimitedApiError",
243                    len: 3,
244                },
245                Token::Str("global"),
246                Token::Bool(false),
247                Token::Str("message"),
248                Token::Str("You are being rate limited."),
249                Token::Str("retry_after"),
250                Token::F64(0.362),
251                Token::StructEnd,
252            ],
253        );
254    }
255}