Skip to main content

ruma_common/api/
error.rs

1//! This module contains types for all kinds of errors that can occur when
2//! converting between http requests / responses and ruma's representation of
3//! matrix API requests / responses.
4
5use 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/// An error returned from a Matrix API endpoint.
22#[derive(Clone, Debug)]
23#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
24pub struct Error {
25    /// The http response's status code.
26    pub status_code: http::StatusCode,
27
28    /// The http response's body.
29    pub body: ErrorBody,
30}
31
32impl Error {
33    /// Constructs a new `Error` with the given status code and body.
34    ///
35    /// This is equivalent to calling `body.into_error(status_code)`.
36    pub fn new(status_code: http::StatusCode, body: ErrorBody) -> Self {
37        Self { status_code, body }
38    }
39
40    /// If `self` is a server error in the `errcode` + `error` format expected
41    /// for client-server API endpoints, returns the error kind (`errcode`).
42    pub fn error_kind(&self) -> Option<&ErrorKind> {
43        as_variant!(&self.body, ErrorBody::Standard(StandardErrorBody { kind, .. }) => kind)
44    }
45}
46
47impl fmt::Display for Error {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        let status_code = self.status_code.as_u16();
50        match &self.body {
51            ErrorBody::Standard(StandardErrorBody { kind, message }) => {
52                let errcode = kind.errcode();
53                write!(f, "[{status_code} / {errcode}] {message}")
54            }
55            ErrorBody::Json(json) => write!(f, "[{status_code}] {json}"),
56            ErrorBody::NotJson { .. } => write!(f, "[{status_code}] <non-json bytes>"),
57        }
58    }
59}
60
61impl StdError for Error {}
62
63impl OutgoingResponse for Error {
64    fn try_into_http_response<T: Default + BufMut>(
65        self,
66    ) -> Result<http::Response<T>, IntoHttpError> {
67        let mut builder = http::Response::builder()
68            .header(http::header::CONTENT_TYPE, ruma_common::http_headers::APPLICATION_JSON)
69            .status(self.status_code);
70
71        // Add data in headers.
72        if let Some(ErrorKind::LimitExceeded(LimitExceededErrorData {
73            retry_after: Some(retry_after),
74        })) = self.error_kind()
75        {
76            let header_value = http::HeaderValue::try_from(retry_after)?;
77            builder = builder.header(http::header::RETRY_AFTER, header_value);
78        }
79
80        builder
81            .body(match self.body {
82                ErrorBody::Standard(standard_body) => {
83                    ruma_common::serde::json_to_buf(&standard_body)?
84                }
85                ErrorBody::Json(json) => ruma_common::serde::json_to_buf(&json)?,
86                ErrorBody::NotJson { .. } => {
87                    return Err(IntoHttpError::Json(serde::ser::Error::custom(
88                        "attempted to serialize ErrorBody::NotJson",
89                    )));
90                }
91            })
92            .map_err(Into::into)
93    }
94}
95
96impl EndpointError for Error {
97    fn from_http_response<T: AsRef<[u8]>>(response: http::Response<T>) -> Self {
98        let status = response.status();
99
100        let body_bytes = &response.body().as_ref();
101        let error_body: ErrorBody = match from_json_slice::<StandardErrorBody>(body_bytes) {
102            Ok(mut standard_body) => {
103                let headers = response.headers();
104
105                if let ErrorKind::LimitExceeded(LimitExceededErrorData { retry_after }) =
106                    &mut standard_body.kind
107                {
108                    // The Retry-After header takes precedence over the retry_after_ms field in
109                    // the body.
110                    if let Some(Ok(retry_after_header)) =
111                        headers.get(http::header::RETRY_AFTER).map(RetryAfter::try_from)
112                    {
113                        *retry_after = Some(retry_after_header);
114                    }
115                }
116
117                ErrorBody::Standard(standard_body)
118            }
119            Err(_) => match from_json_slice(body_bytes) {
120                Ok(json) => ErrorBody::Json(json),
121                Err(error) => ErrorBody::NotJson {
122                    bytes: Bytes::copy_from_slice(body_bytes),
123                    deserialization_error: Arc::new(error),
124                },
125            },
126        };
127
128        error_body.into_error(status)
129    }
130}
131
132/// The body of a Matrix API endpoint error.
133#[derive(Debug, Clone)]
134#[allow(clippy::exhaustive_enums)]
135pub enum ErrorBody {
136    /// A JSON body with the fields expected for Matrix endpoints errors.
137    Standard(StandardErrorBody),
138
139    /// A JSON body with an unexpected structure.
140    Json(JsonValue),
141
142    /// A response body that is not valid JSON.
143    NotJson {
144        /// The raw bytes of the response body.
145        bytes: Bytes,
146
147        /// The error from trying to deserialize the bytes as JSON.
148        deserialization_error: Arc<serde_json::Error>,
149    },
150}
151
152impl ErrorBody {
153    /// Convert the ErrorBody into an Error by adding the http status code.
154    ///
155    /// This is equivalent to calling `Error::new(status_code, self)`.
156    pub fn into_error(self, status_code: http::StatusCode) -> Error {
157        Error { status_code, body: self }
158    }
159}
160
161/// A JSON body with the fields expected for Matrix API endpoints errors.
162#[derive(Clone, Debug, Deserialize, Serialize)]
163#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
164pub struct StandardErrorBody {
165    /// A value which can be used to handle an error message.
166    #[serde(flatten)]
167    pub kind: ErrorKind,
168
169    /// A human-readable error message, usually a sentence explaining what went wrong.
170    #[serde(rename = "error")]
171    pub message: String,
172}
173
174impl StandardErrorBody {
175    /// Construct a new `StandardErrorBody` with the given kind and message.
176    pub fn new(kind: ErrorKind, message: String) -> Self {
177        Self { kind, message }
178    }
179}
180
181/// An error when converting one of ruma's endpoint-specific request or response
182/// types to the corresponding http type.
183#[derive(Debug, Error)]
184#[non_exhaustive]
185pub enum IntoHttpError {
186    /// Failed to add the authentication scheme to the request.
187    #[error("failed to add authentication scheme: {0}")]
188    Authentication(Box<dyn std::error::Error + Send + Sync + 'static>),
189
190    /// Tried to create a request with an old enough version, for which no unstable endpoint
191    /// exists.
192    ///
193    /// This is also a fallback error for if the version is too new for this endpoint.
194    #[error(
195        "endpoint was not supported by server-reported versions, \
196         but no unstable path to fall back to was defined"
197    )]
198    NoUnstablePath,
199
200    /// Tried to create a request with [`MatrixVersion`]s for all of which this endpoint was
201    /// removed.
202    #[error(
203        "could not create any path variant for endpoint, as it was removed in version {}",
204        .0.as_str().expect("no endpoint was removed in Matrix 1.0")
205    )]
206    EndpointRemoved(MatrixVersion),
207
208    /// JSON serialization failed.
209    #[error("JSON serialization failed: {0}")]
210    Json(#[from] serde_json::Error),
211
212    /// Query parameter serialization failed.
213    #[error("query parameter serialization failed: {0}")]
214    Query(#[from] serde_html_form::ser::Error),
215
216    /// Header serialization failed.
217    #[error("header serialization failed: {0}")]
218    Header(#[from] HeaderSerializationError),
219
220    /// HTTP request construction failed.
221    #[error("HTTP request construction failed: {0}")]
222    Http(#[from] http::Error),
223}
224
225impl From<http::header::InvalidHeaderValue> for IntoHttpError {
226    fn from(value: http::header::InvalidHeaderValue) -> Self {
227        Self::Header(value.into())
228    }
229}
230
231/// An error when converting a http request to one of ruma's endpoint-specific request types.
232#[derive(Debug, Error)]
233#[non_exhaustive]
234pub enum FromHttpRequestError {
235    /// Deserialization failed
236    #[error("deserialization failed: {0}")]
237    Deserialization(DeserializationError),
238
239    /// HTTP method mismatch
240    #[error("http method mismatch: expected {expected}, received: {received}")]
241    MethodMismatch {
242        /// expected http method
243        expected: http::method::Method,
244        /// received http method
245        received: http::method::Method,
246    },
247}
248
249impl<T> From<T> for FromHttpRequestError
250where
251    T: Into<DeserializationError>,
252{
253    fn from(err: T) -> Self {
254        Self::Deserialization(err.into())
255    }
256}
257
258/// An error when converting a http response to one of Ruma's endpoint-specific response types.
259#[derive(Debug)]
260#[non_exhaustive]
261pub enum FromHttpResponseError<E> {
262    /// Deserialization failed
263    Deserialization(DeserializationError),
264
265    /// The server returned a non-success status
266    Server(E),
267}
268
269impl<E> FromHttpResponseError<E> {
270    /// Map `FromHttpResponseError<E>` to `FromHttpResponseError<F>` by applying a function to a
271    /// contained `Server` value, leaving a `Deserialization` value untouched.
272    pub fn map<F>(self, f: impl FnOnce(E) -> F) -> FromHttpResponseError<F> {
273        match self {
274            Self::Deserialization(d) => FromHttpResponseError::Deserialization(d),
275            Self::Server(s) => FromHttpResponseError::Server(f(s)),
276        }
277    }
278}
279
280impl<E, F> FromHttpResponseError<Result<E, F>> {
281    /// Transpose `FromHttpResponseError<Result<E, F>>` to `Result<FromHttpResponseError<E>, F>`.
282    pub fn transpose(self) -> Result<FromHttpResponseError<E>, F> {
283        match self {
284            Self::Deserialization(d) => Ok(FromHttpResponseError::Deserialization(d)),
285            Self::Server(s) => s.map(FromHttpResponseError::Server),
286        }
287    }
288}
289
290impl<E: fmt::Display> fmt::Display for FromHttpResponseError<E> {
291    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292        match self {
293            Self::Deserialization(err) => write!(f, "deserialization failed: {err}"),
294            Self::Server(err) => write!(f, "the server returned an error: {err}"),
295        }
296    }
297}
298
299impl<E, T> From<T> for FromHttpResponseError<E>
300where
301    T: Into<DeserializationError>,
302{
303    fn from(err: T) -> Self {
304        Self::Deserialization(err.into())
305    }
306}
307
308impl<E: StdError> StdError for FromHttpResponseError<E> {}
309
310/// Extension trait for `FromHttpResponseError<Error>`.
311pub trait FromHttpResponseErrorExt {
312    /// If `self` is a server error in the `errcode` + `error` format expected
313    /// for Matrix API endpoints, returns the error kind (`errcode`).
314    fn error_kind(&self) -> Option<&ErrorKind>;
315}
316
317impl FromHttpResponseErrorExt for FromHttpResponseError<Error> {
318    fn error_kind(&self) -> Option<&ErrorKind> {
319        as_variant!(self, Self::Server)?.error_kind()
320    }
321}
322
323/// An error when converting a http request / response to one of ruma's endpoint-specific request /
324/// response types.
325#[derive(Debug, Error)]
326#[non_exhaustive]
327pub enum DeserializationError {
328    /// Encountered invalid UTF-8.
329    #[error(transparent)]
330    Utf8(#[from] std::str::Utf8Error),
331
332    /// JSON deserialization failed.
333    #[error(transparent)]
334    Json(#[from] serde_json::Error),
335
336    /// Query parameter deserialization failed.
337    #[error(transparent)]
338    Query(#[from] serde_html_form::de::Error),
339
340    /// Got an invalid identifier.
341    #[error(transparent)]
342    Ident(#[from] crate::IdParseError),
343
344    /// Header value deserialization failed.
345    #[error(transparent)]
346    Header(#[from] HeaderDeserializationError),
347
348    /// Deserialization of `multipart/mixed` response failed.
349    #[error(transparent)]
350    MultipartMixed(#[from] MultipartMixedDeserializationError),
351}
352
353impl From<std::convert::Infallible> for DeserializationError {
354    fn from(err: std::convert::Infallible) -> Self {
355        match err {}
356    }
357}
358
359impl From<http::header::ToStrError> for DeserializationError {
360    fn from(err: http::header::ToStrError) -> Self {
361        Self::Header(HeaderDeserializationError::ToStrError(err))
362    }
363}
364
365/// An error when deserializing the HTTP headers.
366#[derive(Debug, Error)]
367#[non_exhaustive]
368pub enum HeaderDeserializationError {
369    /// Failed to convert `http::header::HeaderValue` to `str`.
370    #[error("{0}")]
371    ToStrError(#[from] http::header::ToStrError),
372
373    /// Failed to convert `http::header::HeaderValue` to an integer.
374    #[error("{0}")]
375    ParseIntError(#[from] ParseIntError),
376
377    /// Failed to parse a HTTP date from a `http::header::Value`.
378    #[error("failed to parse HTTP date")]
379    InvalidHttpDate,
380
381    /// The given required header is missing.
382    #[error("missing header `{0}`")]
383    MissingHeader(String),
384
385    /// The given header failed to parse.
386    #[error("invalid header: {0}")]
387    InvalidHeader(Box<dyn std::error::Error + Send + Sync + 'static>),
388
389    /// A header was received with a unexpected value.
390    #[error(
391        "The {header} header was received with an unexpected value, \
392         expected {expected}, received {unexpected}"
393    )]
394    InvalidHeaderValue {
395        /// The name of the header containing the invalid value.
396        header: String,
397        /// The value the header should have been set to.
398        expected: String,
399        /// The value we instead received and rejected.
400        unexpected: String,
401    },
402
403    /// The `Content-Type` header for a `multipart/mixed` response is missing the `boundary`
404    /// attribute.
405    #[error(
406        "The `Content-Type` header for a `multipart/mixed` response is missing the `boundary` attribute"
407    )]
408    MissingMultipartBoundary,
409}
410
411/// An error when deserializing a `multipart/mixed` response.
412#[derive(Debug, Error)]
413#[non_exhaustive]
414pub enum MultipartMixedDeserializationError {
415    /// There were not the number of body parts that were expected.
416    #[error(
417        "multipart/mixed response does not have enough body parts, \
418         expected {expected}, found {found}"
419    )]
420    MissingBodyParts {
421        /// The number of body parts expected in the response.
422        expected: usize,
423        /// The number of body parts found in the received response.
424        found: usize,
425    },
426
427    /// The separator between the headers and the content of a body part is missing.
428    #[error("multipart/mixed body part is missing separator between headers and content")]
429    MissingBodyPartInnerSeparator,
430
431    /// The separator between a header's name and value is missing.
432    #[error("multipart/mixed body part header is missing separator between name and value")]
433    MissingHeaderSeparator,
434
435    /// A header failed to parse.
436    #[error("invalid multipart/mixed header: {0}")]
437    InvalidHeader(Box<dyn std::error::Error + Send + Sync + 'static>),
438}
439
440/// An error that happens when Ruma cannot understand a Matrix version.
441#[derive(Debug)]
442#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
443pub struct UnknownVersionError;
444
445impl fmt::Display for UnknownVersionError {
446    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447        write!(f, "version string was unknown")
448    }
449}
450
451impl StdError for UnknownVersionError {}
452
453/// An error that happens when an incorrect amount of arguments have been passed to [`PathBuilder`]
454/// parts formatting.
455///
456/// [`PathBuilder`]: super::path_builder::PathBuilder
457#[derive(Debug)]
458#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
459pub struct IncorrectArgumentCount {
460    /// The expected amount of arguments.
461    pub expected: usize,
462
463    /// The amount of arguments received.
464    pub got: usize,
465}
466
467impl fmt::Display for IncorrectArgumentCount {
468    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469        write!(f, "incorrect path argument count, expected {}, got {}", self.expected, self.got)
470    }
471}
472
473impl StdError for IncorrectArgumentCount {}
474
475/// An error when serializing the HTTP headers.
476#[derive(Debug, Error)]
477#[non_exhaustive]
478pub enum HeaderSerializationError {
479    /// Failed to convert a header value to `http::header::HeaderValue`.
480    #[error(transparent)]
481    ToHeaderValue(#[from] http::header::InvalidHeaderValue),
482
483    /// The `SystemTime` could not be converted to a HTTP date.
484    ///
485    /// This only happens if the `SystemTime` provided is too far in the past (before the Unix
486    /// epoch) or the future (after the year 9999).
487    #[error("invalid HTTP date")]
488    InvalidHttpDate,
489}