1use std::{error::Error as StdError, fmt, num::ParseIntError, sync::Arc};
6
7use bytes::{BufMut, Bytes};
8use serde_json::{from_slice as from_json_slice, Value as JsonValue};
9use thiserror::Error;
10
11use super::{EndpointError, MatrixVersion, OutgoingResponse};
12
13#[allow(clippy::exhaustive_structs)]
17#[derive(Clone, Debug)]
18pub struct MatrixError {
19    pub status_code: http::StatusCode,
21
22    pub body: MatrixErrorBody,
24}
25
26impl fmt::Display for MatrixError {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        let status_code = self.status_code.as_u16();
29        match &self.body {
30            MatrixErrorBody::Json(json) => write!(f, "[{status_code}] {json}"),
31            MatrixErrorBody::NotJson { .. } => write!(f, "[{status_code}] <non-json bytes>"),
32        }
33    }
34}
35
36impl StdError for MatrixError {}
37
38impl OutgoingResponse for MatrixError {
39    fn try_into_http_response<T: Default + BufMut>(
40        self,
41    ) -> Result<http::Response<T>, IntoHttpError> {
42        http::Response::builder()
43            .header(http::header::CONTENT_TYPE, "application/json")
44            .status(self.status_code)
45            .body(match self.body {
46                MatrixErrorBody::Json(json) => crate::serde::json_to_buf(&json)?,
47                MatrixErrorBody::NotJson { .. } => {
48                    return Err(IntoHttpError::Json(serde::ser::Error::custom(
49                        "attempted to serialize MatrixErrorBody::NotJson",
50                    )));
51                }
52            })
53            .map_err(Into::into)
54    }
55}
56
57impl EndpointError for MatrixError {
58    fn from_http_response<T: AsRef<[u8]>>(response: http::Response<T>) -> Self {
59        let status_code = response.status();
60        let body = MatrixErrorBody::from_bytes(response.body().as_ref());
61        Self { status_code, body }
62    }
63}
64
65#[derive(Clone, Debug)]
67#[allow(clippy::exhaustive_enums)]
68pub enum MatrixErrorBody {
69    Json(JsonValue),
71
72    NotJson {
74        bytes: Bytes,
76
77        deserialization_error: Arc<serde_json::Error>,
79    },
80}
81
82impl MatrixErrorBody {
83    pub fn from_bytes(body_bytes: &[u8]) -> Self {
85        match from_json_slice(body_bytes) {
86            Ok(json) => MatrixErrorBody::Json(json),
87            Err(e) => MatrixErrorBody::NotJson {
88                bytes: Bytes::copy_from_slice(body_bytes),
89                deserialization_error: Arc::new(e),
90            },
91        }
92    }
93}
94
95#[derive(Debug, Error)]
98#[non_exhaustive]
99pub enum IntoHttpError {
100    #[error("no access token given, but this endpoint requires one")]
102    NeedsAuthentication,
103
104    #[error(
109        "endpoint was not supported by server-reported versions, \
110         but no unstable path to fall back to was defined"
111    )]
112    NoUnstablePath,
113
114    #[error("could not create any path variant for endpoint, as it was removed in version {0}")]
117    EndpointRemoved(MatrixVersion),
118
119    #[error("JSON serialization failed: {0}")]
121    Json(#[from] serde_json::Error),
122
123    #[error("query parameter serialization failed: {0}")]
125    Query(#[from] serde_html_form::ser::Error),
126
127    #[error("header serialization failed: {0}")]
129    Header(#[from] HeaderSerializationError),
130
131    #[error("HTTP request construction failed: {0}")]
133    Http(#[from] http::Error),
134}
135
136impl From<http::header::InvalidHeaderValue> for IntoHttpError {
137    fn from(value: http::header::InvalidHeaderValue) -> Self {
138        Self::Header(value.into())
139    }
140}
141
142#[derive(Debug, Error)]
144#[non_exhaustive]
145pub enum FromHttpRequestError {
146    #[error("deserialization failed: {0}")]
148    Deserialization(DeserializationError),
149
150    #[error("http method mismatch: expected {expected}, received: {received}")]
152    MethodMismatch {
153        expected: http::method::Method,
155        received: http::method::Method,
157    },
158}
159
160impl<T> From<T> for FromHttpRequestError
161where
162    T: Into<DeserializationError>,
163{
164    fn from(err: T) -> Self {
165        Self::Deserialization(err.into())
166    }
167}
168
169#[derive(Debug)]
171#[non_exhaustive]
172pub enum FromHttpResponseError<E> {
173    Deserialization(DeserializationError),
175
176    Server(E),
178}
179
180impl<E> FromHttpResponseError<E> {
181    pub fn map<F>(self, f: impl FnOnce(E) -> F) -> FromHttpResponseError<F> {
184        match self {
185            Self::Deserialization(d) => FromHttpResponseError::Deserialization(d),
186            Self::Server(s) => FromHttpResponseError::Server(f(s)),
187        }
188    }
189}
190
191impl<E, F> FromHttpResponseError<Result<E, F>> {
192    pub fn transpose(self) -> Result<FromHttpResponseError<E>, F> {
194        match self {
195            Self::Deserialization(d) => Ok(FromHttpResponseError::Deserialization(d)),
196            Self::Server(s) => s.map(FromHttpResponseError::Server),
197        }
198    }
199}
200
201impl<E: fmt::Display> fmt::Display for FromHttpResponseError<E> {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        match self {
204            Self::Deserialization(err) => write!(f, "deserialization failed: {err}"),
205            Self::Server(err) => write!(f, "the server returned an error: {err}"),
206        }
207    }
208}
209
210impl<E, T> From<T> for FromHttpResponseError<E>
211where
212    T: Into<DeserializationError>,
213{
214    fn from(err: T) -> Self {
215        Self::Deserialization(err.into())
216    }
217}
218
219impl<E: StdError> StdError for FromHttpResponseError<E> {}
220
221#[derive(Debug, Error)]
224#[non_exhaustive]
225pub enum DeserializationError {
226    #[error(transparent)]
228    Utf8(#[from] std::str::Utf8Error),
229
230    #[error(transparent)]
232    Json(#[from] serde_json::Error),
233
234    #[error(transparent)]
236    Query(#[from] serde_html_form::de::Error),
237
238    #[error(transparent)]
240    Ident(#[from] crate::IdParseError),
241
242    #[error(transparent)]
244    Header(#[from] HeaderDeserializationError),
245
246    #[error(transparent)]
248    MultipartMixed(#[from] MultipartMixedDeserializationError),
249}
250
251impl From<std::convert::Infallible> for DeserializationError {
252    fn from(err: std::convert::Infallible) -> Self {
253        match err {}
254    }
255}
256
257impl From<http::header::ToStrError> for DeserializationError {
258    fn from(err: http::header::ToStrError) -> Self {
259        Self::Header(HeaderDeserializationError::ToStrError(err))
260    }
261}
262
263#[derive(Debug, Error)]
265#[non_exhaustive]
266pub enum HeaderDeserializationError {
267    #[error("{0}")]
269    ToStrError(#[from] http::header::ToStrError),
270
271    #[error("{0}")]
273    ParseIntError(#[from] ParseIntError),
274
275    #[error("failed to parse HTTP date")]
277    InvalidHttpDate,
278
279    #[error("missing header `{0}`")]
281    MissingHeader(String),
282
283    #[error("invalid header: {0}")]
285    InvalidHeader(Box<dyn std::error::Error + Send + Sync + 'static>),
286
287    #[error(
289        "The {header} header was received with an unexpected value, \
290         expected {expected}, received {unexpected}"
291    )]
292    InvalidHeaderValue {
293        header: String,
295        expected: String,
297        unexpected: String,
299    },
300
301    #[error(
304        "The `Content-Type` header for a `multipart/mixed` response is missing the `boundary` attribute"
305    )]
306    MissingMultipartBoundary,
307}
308
309#[derive(Debug, Error)]
311#[non_exhaustive]
312pub enum MultipartMixedDeserializationError {
313    #[error(
315        "multipart/mixed response does not have enough body parts, \
316         expected {expected}, found {found}"
317    )]
318    MissingBodyParts {
319        expected: usize,
321        found: usize,
323    },
324
325    #[error("multipart/mixed body part is missing separator between headers and content")]
327    MissingBodyPartInnerSeparator,
328
329    #[error("multipart/mixed body part header is missing separator between name and value")]
331    MissingHeaderSeparator,
332
333    #[error("invalid multipart/mixed header: {0}")]
335    InvalidHeader(Box<dyn std::error::Error + Send + Sync + 'static>),
336}
337
338#[derive(Debug)]
340#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
341pub struct UnknownVersionError;
342
343impl fmt::Display for UnknownVersionError {
344    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345        write!(f, "version string was unknown")
346    }
347}
348
349impl StdError for UnknownVersionError {}
350
351#[derive(Debug)]
354#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
355pub struct IncorrectArgumentCount {
356    pub expected: usize,
358
359    pub got: usize,
361}
362
363impl fmt::Display for IncorrectArgumentCount {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        write!(f, "incorrect path argument count, expected {}, got {}", self.expected, self.got)
366    }
367}
368
369impl StdError for IncorrectArgumentCount {}
370
371#[derive(Debug, Error)]
373#[non_exhaustive]
374pub enum HeaderSerializationError {
375    #[error(transparent)]
377    ToHeaderValue(#[from] http::header::InvalidHeaderValue),
378
379    #[error("invalid HTTP date")]
384    InvalidHttpDate,
385}