ruma_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};
6
7use bytes::BufMut;
8use serde_json::{from_slice as from_json_slice, Value as JsonValue};
9use thiserror::Error;
10
11use crate::{EndpointError, MatrixVersion, OutgoingResponse};
12
13/// A general-purpose Matrix error type consisting of an HTTP status code and a JSON body.
14///
15/// Note that individual `ruma-*-api` crates may provide more specific error types.
16#[allow(clippy::exhaustive_structs)]
17#[derive(Clone, Debug)]
18pub struct MatrixError {
19    /// The http response's status code.
20    pub status_code: http::StatusCode,
21
22    /// The http response's body.
23    pub body: JsonValue,
24}
25
26impl fmt::Display for MatrixError {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write!(f, "[{}] ", self.status_code.as_u16())?;
29        fmt::Display::fmt(&self.body, f)
30    }
31}
32
33impl StdError for MatrixError {}
34
35impl OutgoingResponse for MatrixError {
36    fn try_into_http_response<T: Default + BufMut>(
37        self,
38    ) -> Result<http::Response<T>, IntoHttpError> {
39        http::Response::builder()
40            .header(http::header::CONTENT_TYPE, "application/json")
41            .status(self.status_code)
42            .body(ruma_serde::json_to_buf(&self.body)?)
43            .map_err(Into::into)
44    }
45}
46
47impl EndpointError for MatrixError {
48    fn try_from_http_response<T: AsRef<[u8]>>(
49        response: http::Response<T>,
50    ) -> Result<Self, DeserializationError> {
51        Ok(Self {
52            status_code: response.status(),
53            body: from_json_slice(response.body().as_ref())?,
54        })
55    }
56}
57
58/// An error when converting one of ruma's endpoint-specific request or response
59/// types to the corresponding http type.
60#[derive(Debug, Error)]
61#[non_exhaustive]
62pub enum IntoHttpError {
63    /// Tried to create an authentication request without an access token.
64    #[error(
65        "This endpoint has to be converted to http::Request using \
66         try_into_authenticated_http_request"
67    )]
68    NeedsAuthentication,
69
70    /// Tried to create a request with an old enough version, for which no unstable endpoint
71    /// exists.
72    ///
73    /// This is also a fallback error for if the version is too new for this endpoint.
74    #[error("Endpoint was not supported by server-reported versions, but no unstable path to fall back to was defined.")]
75    NoUnstablePath,
76
77    /// Tried to create a request with [`MatrixVersion`]s for all of which this endpoint was
78    /// removed.
79    #[error("Could not create any path variant for endpoint, as it was removed in version {0}")]
80    EndpointRemoved(MatrixVersion),
81
82    /// JSON serialization failed.
83    #[error("JSON serialization failed: {0}")]
84    Json(#[from] serde_json::Error),
85
86    /// Query parameter serialization failed.
87    #[error("Query parameter serialization failed: {0}")]
88    Query(#[from] ruma_serde::urlencoded::ser::Error),
89
90    /// Header serialization failed.
91    #[error("Header serialization failed: {0}")]
92    Header(#[from] http::header::InvalidHeaderValue),
93
94    /// HTTP request construction failed.
95    #[error("HTTP request construction failed: {0}")]
96    Http(#[from] http::Error),
97}
98
99/// An error when converting a http request to one of ruma's endpoint-specific request types.
100#[derive(Debug, Error)]
101#[non_exhaustive]
102pub enum FromHttpRequestError {
103    /// Deserialization failed
104    #[error("deserialization failed: {0}")]
105    Deserialization(DeserializationError),
106
107    /// HTTP method mismatch
108    #[error("http method mismatch: expected {expected}, received: {received}")]
109    MethodMismatch {
110        /// expected http method
111        expected: http::method::Method,
112        /// received http method
113        received: http::method::Method,
114    },
115}
116
117impl<T> From<T> for FromHttpRequestError
118where
119    T: Into<DeserializationError>,
120{
121    fn from(err: T) -> Self {
122        Self::Deserialization(err.into())
123    }
124}
125
126/// An error when converting a http response to one of Ruma's endpoint-specific response types.
127#[derive(Debug)]
128#[non_exhaustive]
129pub enum FromHttpResponseError<E> {
130    /// Deserialization failed
131    Deserialization(DeserializationError),
132
133    /// The server returned a non-success status
134    Http(ServerError<E>),
135}
136
137impl<E: fmt::Display> fmt::Display for FromHttpResponseError<E> {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        match self {
140            Self::Deserialization(err) => write!(f, "deserialization failed: {}", err),
141            Self::Http(err) => write!(f, "the server returned an error: {}", err),
142        }
143    }
144}
145
146impl<E> From<ServerError<E>> for FromHttpResponseError<E> {
147    fn from(err: ServerError<E>) -> Self {
148        Self::Http(err)
149    }
150}
151
152impl<E, T> From<T> for FromHttpResponseError<E>
153where
154    T: Into<DeserializationError>,
155{
156    fn from(err: T) -> Self {
157        Self::Deserialization(err.into())
158    }
159}
160
161impl<E: StdError> StdError for FromHttpResponseError<E> {}
162
163/// An error was reported by the server (HTTP status code 4xx or 5xx)
164#[derive(Debug)]
165#[allow(clippy::exhaustive_enums)]
166pub enum ServerError<E> {
167    /// An error that is expected to happen under certain circumstances and
168    /// that has a well-defined structure
169    Known(E),
170
171    /// An error of unexpected type of structure
172    Unknown(DeserializationError),
173}
174
175impl<E: fmt::Display> fmt::Display for ServerError<E> {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        match self {
178            ServerError::Known(e) => fmt::Display::fmt(e, f),
179            ServerError::Unknown(res_err) => fmt::Display::fmt(res_err, f),
180        }
181    }
182}
183
184impl<E: StdError> StdError for ServerError<E> {}
185
186/// An error when converting a http request / response to one of ruma's endpoint-specific request /
187/// response types.
188#[derive(Debug, Error)]
189#[non_exhaustive]
190pub enum DeserializationError {
191    /// Encountered invalid UTF-8.
192    #[error("{0}")]
193    Utf8(#[from] std::str::Utf8Error),
194
195    /// JSON deserialization failed.
196    #[error("{0}")]
197    Json(#[from] serde_json::Error),
198
199    /// Query parameter deserialization failed.
200    #[error("{0}")]
201    Query(#[from] ruma_serde::urlencoded::de::Error),
202
203    /// Got an invalid identifier.
204    #[error("{0}")]
205    Ident(#[from] ruma_identifiers::Error),
206
207    /// Header value deserialization failed.
208    #[error("{0}")]
209    Header(#[from] HeaderDeserializationError),
210}
211
212impl From<std::convert::Infallible> for DeserializationError {
213    fn from(err: std::convert::Infallible) -> Self {
214        match err {}
215    }
216}
217
218impl From<http::header::ToStrError> for DeserializationError {
219    fn from(err: http::header::ToStrError) -> Self {
220        Self::Header(HeaderDeserializationError::ToStrError(err))
221    }
222}
223
224/// An error with the http headers.
225#[derive(Debug, Error)]
226#[non_exhaustive]
227pub enum HeaderDeserializationError {
228    /// Failed to convert `http::header::HeaderValue` to `str`.
229    #[error("{0}")]
230    ToStrError(http::header::ToStrError),
231
232    /// The given required header is missing.
233    #[error("Missing header `{0}`")]
234    MissingHeader(String),
235}
236
237/// An error that happens when Ruma cannot understand a Matrix version.
238#[derive(Debug)]
239#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
240pub struct UnknownVersionError;
241
242impl fmt::Display for UnknownVersionError {
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        write!(f, "Version string was unknown.")
245    }
246}
247
248impl StdError for UnknownVersionError {}