1use std::error::Error as StdError;
2use std::fmt;
3
4use reqwest::header::InvalidHeaderValue;
5use reqwest::{Error as ReqwestError, Method, Response, StatusCode};
6use serde::de::{Deserialize, Deserializer, Error as _};
7use url::ParseError as UrlError;
8
9use crate::internal::prelude::*;
10use crate::json::*;
11
12#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
13#[non_exhaustive]
14pub struct DiscordJsonError {
15 pub code: isize,
17 pub message: String,
19 #[serde(default, deserialize_with = "deserialize_errors")]
21 pub errors: Vec<DiscordJsonSingleError>,
22}
23
24#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
25pub struct DiscordJsonSingleError {
26 pub code: String,
28 pub message: String,
30 pub path: String,
32}
33
34#[derive(Clone, Debug, Eq, PartialEq)]
35#[non_exhaustive]
36pub struct ErrorResponse {
37 pub status_code: StatusCode,
38 pub url: String,
39 pub method: Method,
40 pub error: DiscordJsonError,
41}
42
43impl ErrorResponse {
44 pub async fn from_response(r: Response, method: Method) -> Self {
46 ErrorResponse {
47 status_code: r.status(),
48 url: r.url().to_string(),
49 method,
50 error: decode_resp(r).await.unwrap_or_else(|e| DiscordJsonError {
51 code: -1,
52 message: format!("[Serenity] Could not decode json when receiving error response from discord:, {e}"),
53 errors: vec![],
54 }),
55 }
56 }
57}
58
59#[derive(Debug)]
60#[non_exhaustive]
61pub enum HttpError {
62 UnsuccessfulRequest(ErrorResponse),
64 RateLimitI64F64,
67 RateLimitUtf8,
69 Url(UrlError),
71 InvalidWebhook,
73 InvalidHeader(InvalidHeaderValue),
75 Request(ReqwestError),
77 InvalidScheme,
79 InvalidPort,
81 ApplicationIdMissing,
83}
84
85impl HttpError {
86 #[must_use]
88 pub fn is_unsuccessful_request(&self) -> bool {
89 matches!(self, Self::UnsuccessfulRequest(_))
90 }
91
92 #[must_use]
94 pub fn is_url_error(&self) -> bool {
95 matches!(self, Self::Url(_))
96 }
97
98 #[must_use]
100 pub fn is_invalid_header(&self) -> bool {
101 matches!(self, Self::InvalidHeader(_))
102 }
103
104 #[must_use]
106 pub fn status_code(&self) -> Option<StatusCode> {
107 match self {
108 Self::UnsuccessfulRequest(res) => Some(res.status_code),
109 _ => None,
110 }
111 }
112}
113
114impl From<ErrorResponse> for HttpError {
115 fn from(error: ErrorResponse) -> Self {
116 Self::UnsuccessfulRequest(error)
117 }
118}
119
120impl From<ReqwestError> for HttpError {
121 fn from(error: ReqwestError) -> Self {
122 Self::Request(error)
123 }
124}
125
126impl From<UrlError> for HttpError {
127 fn from(error: UrlError) -> Self {
128 Self::Url(error)
129 }
130}
131
132impl From<InvalidHeaderValue> for HttpError {
133 fn from(error: InvalidHeaderValue) -> Self {
134 Self::InvalidHeader(error)
135 }
136}
137
138impl fmt::Display for HttpError {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 match self {
141 Self::UnsuccessfulRequest(e) => {
142 f.write_str(&e.error.message)?;
143
144 let mut errors_iter = e.error.errors.iter();
146 if let Some(error) = errors_iter.next() {
147 f.write_str(" (")?;
148 f.write_str(&error.path)?;
149 f.write_str(": ")?;
150 f.write_str(&error.message)?;
151 for error in errors_iter {
152 f.write_str(", ")?;
153 f.write_str(&error.path)?;
154 f.write_str(": ")?;
155 f.write_str(&error.message)?;
156 }
157 f.write_str(")")?;
158 }
159
160 Ok(())
161 },
162 Self::RateLimitI64F64 => f.write_str("Error decoding a header into an i64 or f64"),
163 Self::RateLimitUtf8 => f.write_str("Error decoding a header from UTF-8"),
164 Self::Url(_) => f.write_str("Provided URL is incorrect."),
165 Self::InvalidWebhook => f.write_str("Provided URL is not a valid webhook."),
166 Self::InvalidHeader(_) => f.write_str("Provided value is an invalid header value."),
167 Self::Request(_) => f.write_str("Error while sending HTTP request."),
168 Self::InvalidScheme => f.write_str("Invalid Url scheme."),
169 Self::InvalidPort => f.write_str("Invalid port."),
170 Self::ApplicationIdMissing => f.write_str("Application id was expected but missing."),
171 }
172 }
173}
174
175impl StdError for HttpError {
176 fn source(&self) -> Option<&(dyn StdError + 'static)> {
177 match self {
178 Self::Url(inner) => Some(inner),
179 Self::Request(inner) => Some(inner),
180 _ => None,
181 }
182 }
183}
184
185#[allow(clippy::missing_errors_doc)]
186pub fn deserialize_errors<'de, D: Deserializer<'de>>(
187 deserializer: D,
188) -> StdResult<Vec<DiscordJsonSingleError>, D::Error> {
189 let map: Value = Value::deserialize(deserializer)?;
190
191 if !map.is_object() {
192 return Ok(vec![]);
193 }
194
195 let mut errors = Vec::new();
196 let mut path = Vec::new();
197 loop_errors(&map, &mut errors, &mut path).map_err(D::Error::custom)?;
198
199 Ok(errors)
200}
201
202fn make_error(
203 errors_value: &Value,
204 errors: &mut Vec<DiscordJsonSingleError>,
205 path: &[&str],
206) -> StdResult<(), &'static str> {
207 let found_errors = errors_value.as_array().ok_or("expected array")?;
208
209 for error in found_errors {
210 let error_object = error.as_object().ok_or("expected object")?;
211
212 errors.push(DiscordJsonSingleError {
213 code: error_object
214 .get("code")
215 .ok_or("expected code")?
216 .as_str()
217 .ok_or("expected string")?
218 .to_owned(),
219 message: error_object
220 .get("message")
221 .ok_or("expected message")?
222 .as_str()
223 .ok_or("expected string")?
224 .to_owned(),
225 path: path.join("."),
226 });
227 }
228 Ok(())
229}
230
231fn loop_errors<'a>(
232 value: &'a Value,
233 errors: &mut Vec<DiscordJsonSingleError>,
234 path: &mut Vec<&'a str>,
235) -> StdResult<(), &'static str> {
236 for (key, value) in value.as_object().ok_or("expected object")? {
237 if key == "_errors" {
238 make_error(value, errors, path)?;
239 } else {
240 path.push(key);
241 loop_errors(value, errors, path)?;
242 path.pop();
243 }
244 }
245 Ok(())
246}
247
248#[cfg(test)]
249mod test {
250 use http_crate::response::Builder;
251 use reqwest::ResponseBuilderExt;
252
253 use super::*;
254
255 #[tokio::test]
256 async fn test_error_response_into() {
257 let error = DiscordJsonError {
258 code: 43121215,
259 message: String::from("This is a Ferris error"),
260 errors: vec![],
261 };
262
263 let mut builder = Builder::new();
264 builder = builder.status(403);
265 builder = builder.url(String::from("https://ferris.crab").parse().unwrap());
266 let body_string = to_string(&error).unwrap();
267 let response = builder.body(body_string.into_bytes()).unwrap();
268
269 let reqwest_response: reqwest::Response = response.into();
270 let error_response = ErrorResponse::from_response(reqwest_response, Method::POST).await;
271
272 let known = ErrorResponse {
273 status_code: reqwest::StatusCode::from_u16(403).unwrap(),
274 url: String::from("https://ferris.crab/"),
275 method: Method::POST,
276 error,
277 };
278
279 assert_eq!(error_response, known);
280 }
281}