1use std::{error::Error as StdError, fmt, num::ParseIntError, sync::Arc};
6
7use as_variant::as_variant;
8use bytes::{BufMut, Bytes};
9use serde::{Deserialize, Serialize};
10use serde_json::{Value as JsonValue, from_slice as from_json_slice};
11use thiserror::Error;
12
13mod kind;
14mod kind_serde;
15#[cfg(test)]
16mod tests;
17
18pub use self::kind::*;
19use super::{EndpointError, MatrixVersion, OutgoingResponse};
20
21#[derive(Clone, Debug)]
23#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
24pub struct Error {
25 pub status_code: http::StatusCode,
27
28 pub body: ErrorBody,
30}
31
32impl Error {
33 pub fn new(status_code: http::StatusCode, body: ErrorBody) -> Self {
37 Self { status_code, body }
38 }
39
40 pub fn error_kind(&self) -> Option<&ErrorKind> {
42 as_variant!(&self.body, ErrorBody::Standard(StandardErrorBody { kind, .. }) => kind)
43 }
44
45 pub fn is_endpoint_not_implemented(&self) -> bool {
53 self.status_code == http::StatusCode::NOT_FOUND
54 && self
55 .error_kind()
56 .is_some_and(|error_kind| matches!(error_kind, ErrorKind::Unrecognized))
57 }
58}
59
60impl fmt::Display for Error {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 let status_code = self.status_code.as_u16();
63 match &self.body {
64 ErrorBody::Standard(StandardErrorBody { kind, message }) => {
65 let errcode = kind.errcode();
66 write!(f, "[{status_code} / {errcode}] {message}")
67 }
68 ErrorBody::Json(json) => write!(f, "[{status_code}] {json}"),
69 ErrorBody::NotJson { .. } => write!(f, "[{status_code}] <non-json bytes>"),
70 }
71 }
72}
73
74impl StdError for Error {}
75
76impl OutgoingResponse for Error {
77 fn try_into_http_response<T: Default + BufMut>(
78 self,
79 ) -> Result<http::Response<T>, IntoHttpError> {
80 let mut builder = http::Response::builder()
81 .header(http::header::CONTENT_TYPE, ruma_common::http_headers::APPLICATION_JSON)
82 .status(self.status_code);
83
84 if let Some(ErrorKind::LimitExceeded(LimitExceededErrorData {
86 retry_after: Some(retry_after),
87 })) = self.error_kind()
88 {
89 let header_value = http::HeaderValue::try_from(retry_after)?;
90 builder = builder.header(http::header::RETRY_AFTER, header_value);
91 }
92
93 builder
94 .body(match self.body {
95 ErrorBody::Standard(standard_body) => {
96 ruma_common::serde::json_to_buf(&standard_body)?
97 }
98 ErrorBody::Json(json) => ruma_common::serde::json_to_buf(&json)?,
99 ErrorBody::NotJson { .. } => {
100 return Err(IntoHttpError::Json(serde::ser::Error::custom(
101 "attempted to serialize ErrorBody::NotJson",
102 )));
103 }
104 })
105 .map_err(Into::into)
106 }
107}
108
109impl EndpointError for Error {
110 fn from_http_response<T: AsRef<[u8]>>(response: http::Response<T>) -> Self {
111 let status = response.status();
112
113 let body_bytes = &response.body().as_ref();
114 let error_body: ErrorBody = match from_json_slice::<StandardErrorBody>(body_bytes) {
115 Ok(mut standard_body) => {
116 let headers = response.headers();
117
118 if let ErrorKind::LimitExceeded(LimitExceededErrorData { retry_after }) =
119 &mut standard_body.kind
120 {
121 if let Some(Ok(retry_after_header)) =
124 headers.get(http::header::RETRY_AFTER).map(RetryAfter::try_from)
125 {
126 *retry_after = Some(retry_after_header);
127 }
128 }
129
130 ErrorBody::Standard(standard_body)
131 }
132 Err(_) => match from_json_slice(body_bytes) {
133 Ok(json) => ErrorBody::Json(json),
134 Err(error) => ErrorBody::NotJson {
135 bytes: Bytes::copy_from_slice(body_bytes),
136 deserialization_error: Arc::new(error),
137 },
138 },
139 };
140
141 error_body.into_error(status)
142 }
143}
144
145#[derive(Debug, Clone)]
147#[allow(clippy::exhaustive_enums)]
148pub enum ErrorBody {
149 Standard(StandardErrorBody),
151
152 Json(JsonValue),
154
155 NotJson {
157 bytes: Bytes,
159
160 deserialization_error: Arc<serde_json::Error>,
162 },
163}
164
165impl ErrorBody {
166 pub fn into_error(self, status_code: http::StatusCode) -> Error {
170 Error { status_code, body: self }
171 }
172}
173
174#[derive(Clone, Debug, Deserialize, Serialize)]
176#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
177pub struct StandardErrorBody {
178 #[serde(flatten)]
180 pub kind: ErrorKind,
181
182 #[serde(rename = "error")]
184 pub message: String,
185}
186
187impl StandardErrorBody {
188 pub fn new(kind: ErrorKind, message: String) -> Self {
190 Self { kind, message }
191 }
192}
193
194#[derive(Debug, Error)]
197#[non_exhaustive]
198pub enum IntoHttpError {
199 #[error("failed to add authentication scheme: {0}")]
201 Authentication(Box<dyn std::error::Error + Send + Sync + 'static>),
202
203 #[error(
208 "endpoint was not supported by server-reported versions, \
209 but no unstable path to fall back to was defined"
210 )]
211 NoUnstablePath,
212
213 #[error(
216 "could not create any path variant for endpoint, as it was removed in version {}",
217 .0.as_str().expect("no endpoint was removed in Matrix 1.0")
218 )]
219 EndpointRemoved(MatrixVersion),
220
221 #[error("JSON serialization failed: {0}")]
223 Json(#[from] serde_json::Error),
224
225 #[error("query parameter serialization failed: {0}")]
227 Query(#[from] serde_html_form::ser::Error),
228
229 #[error("header serialization failed: {0}")]
231 Header(#[from] HeaderSerializationError),
232
233 #[error("HTTP request construction failed: {0}")]
235 Http(#[from] http::Error),
236}
237
238impl IntoHttpError {
239 pub fn authentication(
241 error: impl Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
242 ) -> Self {
243 Self::Authentication(error.into())
244 }
245}
246
247impl From<http::header::InvalidHeaderValue> for IntoHttpError {
248 fn from(value: http::header::InvalidHeaderValue) -> Self {
249 Self::Header(value.into())
250 }
251}
252
253#[derive(Debug, Error)]
255#[non_exhaustive]
256pub enum FromHttpRequestError {
257 #[error("deserialization failed: {0}")]
259 Deserialization(DeserializationError),
260
261 #[error("http method mismatch: expected {expected}, received: {received}")]
263 MethodMismatch {
264 expected: http::method::Method,
266 received: http::method::Method,
268 },
269}
270
271impl<T> From<T> for FromHttpRequestError
272where
273 T: Into<DeserializationError>,
274{
275 fn from(err: T) -> Self {
276 Self::Deserialization(err.into())
277 }
278}
279
280#[derive(Debug)]
282#[non_exhaustive]
283pub enum FromHttpResponseError<E> {
284 Deserialization(DeserializationError),
286
287 Server(E),
289}
290
291impl<E> FromHttpResponseError<E> {
292 pub fn map<F>(self, f: impl FnOnce(E) -> F) -> FromHttpResponseError<F> {
295 match self {
296 Self::Deserialization(d) => FromHttpResponseError::Deserialization(d),
297 Self::Server(s) => FromHttpResponseError::Server(f(s)),
298 }
299 }
300}
301
302impl<E, F> FromHttpResponseError<Result<E, F>> {
303 pub fn transpose(self) -> Result<FromHttpResponseError<E>, F> {
305 match self {
306 Self::Deserialization(d) => Ok(FromHttpResponseError::Deserialization(d)),
307 Self::Server(s) => s.map(FromHttpResponseError::Server),
308 }
309 }
310}
311
312impl<E: fmt::Display> fmt::Display for FromHttpResponseError<E> {
313 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
314 match self {
315 Self::Deserialization(err) => write!(f, "deserialization failed: {err}"),
316 Self::Server(err) => write!(f, "the server returned an error: {err}"),
317 }
318 }
319}
320
321impl<E, T> From<T> for FromHttpResponseError<E>
322where
323 T: Into<DeserializationError>,
324{
325 fn from(err: T) -> Self {
326 Self::Deserialization(err.into())
327 }
328}
329
330impl<E: StdError> StdError for FromHttpResponseError<E> {}
331
332pub trait FromHttpResponseErrorExt {
334 fn error_kind(&self) -> Option<&ErrorKind>;
337}
338
339impl FromHttpResponseErrorExt for FromHttpResponseError<Error> {
340 fn error_kind(&self) -> Option<&ErrorKind> {
341 as_variant!(self, Self::Server)?.error_kind()
342 }
343}
344
345#[derive(Debug, Error)]
348#[non_exhaustive]
349pub enum DeserializationError {
350 #[error(transparent)]
352 Utf8(#[from] std::str::Utf8Error),
353
354 #[error(transparent)]
356 Json(#[from] serde_json::Error),
357
358 #[error(transparent)]
360 Query(#[from] serde_html_form::de::Error),
361
362 #[error(transparent)]
364 Ident(#[from] crate::IdParseError),
365
366 #[error(transparent)]
368 Header(#[from] HeaderDeserializationError),
369
370 #[error(transparent)]
372 MultipartMixed(#[from] MultipartMixedDeserializationError),
373}
374
375impl From<std::convert::Infallible> for DeserializationError {
376 fn from(err: std::convert::Infallible) -> Self {
377 match err {}
378 }
379}
380
381impl From<http::header::ToStrError> for DeserializationError {
382 fn from(err: http::header::ToStrError) -> Self {
383 Self::Header(HeaderDeserializationError::ToStrError(err))
384 }
385}
386
387#[derive(Debug, Error)]
389#[non_exhaustive]
390pub enum HeaderDeserializationError {
391 #[error("{0}")]
393 ToStrError(#[from] http::header::ToStrError),
394
395 #[error("{0}")]
397 ParseIntError(#[from] ParseIntError),
398
399 #[error("failed to parse HTTP date")]
401 InvalidHttpDate,
402
403 #[error("missing header `{0}`")]
405 MissingHeader(String),
406
407 #[error("invalid header: {0}")]
409 InvalidHeader(Box<dyn std::error::Error + Send + Sync + 'static>),
410
411 #[error(
413 "The {header} header was received with an unexpected value, \
414 expected {expected}, received {unexpected}"
415 )]
416 InvalidHeaderValue {
417 header: String,
419 expected: String,
421 unexpected: String,
423 },
424
425 #[error(
428 "The `Content-Type` header for a `multipart/mixed` response is missing the `boundary` attribute"
429 )]
430 MissingMultipartBoundary,
431}
432
433#[derive(Debug, Error)]
435#[non_exhaustive]
436pub enum MultipartMixedDeserializationError {
437 #[error(
439 "multipart/mixed response does not have enough body parts, \
440 expected {expected}, found {found}"
441 )]
442 MissingBodyParts {
443 expected: usize,
445 found: usize,
447 },
448
449 #[error("multipart/mixed body part is missing separator between headers and content")]
451 MissingBodyPartInnerSeparator,
452
453 #[error("multipart/mixed body part header is missing separator between name and value")]
455 MissingHeaderSeparator,
456
457 #[error("invalid multipart/mixed header: {0}")]
459 InvalidHeader(Box<dyn std::error::Error + Send + Sync + 'static>),
460}
461
462#[derive(Debug)]
464#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
465pub struct UnknownVersionError;
466
467impl fmt::Display for UnknownVersionError {
468 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469 write!(f, "version string was unknown")
470 }
471}
472
473impl StdError for UnknownVersionError {}
474
475#[derive(Debug)]
480#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
481pub struct IncorrectArgumentCount {
482 pub expected: usize,
484
485 pub got: usize,
487}
488
489impl fmt::Display for IncorrectArgumentCount {
490 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
491 write!(f, "incorrect path argument count, expected {}, got {}", self.expected, self.got)
492 }
493}
494
495impl StdError for IncorrectArgumentCount {}
496
497#[derive(Debug, Error)]
499#[non_exhaustive]
500pub enum HeaderSerializationError {
501 #[error(transparent)]
503 ToHeaderValue(#[from] http::header::InvalidHeaderValue),
504
505 #[error("invalid HTTP date")]
510 InvalidHttpDate,
511}