1use std::{error::Error as StdError, fmt, num::ParseIntError, sync::Arc};
6
7use as_variant::as_variant;
8use bytes::Bytes;
9use ruma_macros::OutgoingBodyJson;
10use serde::{Deserialize, Serialize};
11use serde_json::{Value as JsonValue, from_slice as from_json_slice};
12use thiserror::Error;
13
14mod kind;
15mod kind_serde;
16#[cfg(test)]
17mod tests;
18
19pub use self::kind::*;
20use super::{EndpointError, MatrixVersion, OutgoingResponse};
21
22#[derive(Clone, Debug)]
24#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
25pub struct Error {
26 pub status_code: http::StatusCode,
28
29 pub body: ErrorBody,
31}
32
33impl Error {
34 pub fn new(status_code: http::StatusCode, body: ErrorBody) -> Self {
38 Self { status_code, body }
39 }
40
41 pub fn error_kind(&self) -> Option<&ErrorKind> {
43 as_variant!(&self.body, ErrorBody::Standard(StandardErrorBody { kind, .. }) => kind)
44 }
45
46 pub fn is_endpoint_not_implemented(&self) -> bool {
54 self.status_code == http::StatusCode::NOT_FOUND
55 && self
56 .error_kind()
57 .is_some_and(|error_kind| matches!(error_kind, ErrorKind::Unrecognized))
58 }
59}
60
61impl fmt::Display for Error {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 let status_code = self.status_code.as_u16();
64 match &self.body {
65 ErrorBody::Standard(StandardErrorBody { kind, message }) => {
66 let errcode = kind.errcode();
67 write!(f, "[{status_code} / {errcode}] {message}")
68 }
69 ErrorBody::Json(json) => write!(f, "[{status_code}] {json}"),
70 ErrorBody::NotJson { .. } => write!(f, "[{status_code}] <non-json bytes>"),
71 }
72 }
73}
74
75impl StdError for Error {}
76
77impl OutgoingResponse for Error {
78 type Body = ErrorResponseBody;
79
80 fn try_into_http_response_inner(self) -> Result<http::Response<Self::Body>, IntoHttpError> {
81 let mut builder = http::Response::builder()
82 .header(http::header::CONTENT_TYPE, ruma_common::http_headers::APPLICATION_JSON)
83 .status(self.status_code);
84
85 if let Some(ErrorKind::LimitExceeded(LimitExceededErrorData {
87 retry_after: Some(retry_after),
88 })) = self.error_kind()
89 {
90 let header_value = http::HeaderValue::try_from(retry_after)?;
91 builder = builder.header(http::header::RETRY_AFTER, header_value);
92 }
93
94 builder.body(ErrorResponseBody(self.body)).map_err(Into::into)
95 }
96}
97
98impl EndpointError for Error {
99 fn from_http_response(response: http::Response<&[u8]>) -> Self {
100 let status = response.status();
101
102 let body_bytes = response.body();
103 let error_body: ErrorBody = match from_json_slice::<StandardErrorBody>(body_bytes) {
104 Ok(mut standard_body) => {
105 let headers = response.headers();
106
107 if let ErrorKind::LimitExceeded(LimitExceededErrorData { retry_after }) =
108 &mut standard_body.kind
109 {
110 if let Some(Ok(retry_after_header)) =
113 headers.get(http::header::RETRY_AFTER).map(RetryAfter::try_from)
114 {
115 *retry_after = Some(retry_after_header);
116 }
117 }
118
119 ErrorBody::Standard(standard_body)
120 }
121 Err(_) => match from_json_slice(body_bytes) {
122 Ok(json) => ErrorBody::Json(json),
123 Err(error) => ErrorBody::NotJson {
124 bytes: Bytes::copy_from_slice(body_bytes),
125 deserialization_error: Arc::new(error),
126 },
127 },
128 };
129
130 error_body.into_error(status)
131 }
132}
133
134#[derive(Debug, Clone)]
136#[allow(clippy::exhaustive_enums)]
137pub enum ErrorBody {
138 Standard(StandardErrorBody),
140
141 Json(JsonValue),
143
144 NotJson {
146 bytes: Bytes,
148
149 deserialization_error: Arc<serde_json::Error>,
151 },
152}
153
154impl ErrorBody {
155 pub fn into_error(self, status_code: http::StatusCode) -> Error {
159 Error { status_code, body: self }
160 }
161}
162
163#[derive(Clone, Debug, Deserialize, Serialize)]
165#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
166pub struct StandardErrorBody {
167 #[serde(flatten)]
169 pub kind: ErrorKind,
170
171 #[serde(rename = "error")]
173 pub message: String,
174}
175
176impl StandardErrorBody {
177 pub fn new(kind: ErrorKind, message: String) -> Self {
179 Self { kind, message }
180 }
181}
182
183#[doc(hidden)]
188#[derive(OutgoingBodyJson)]
189pub struct ErrorResponseBody(ErrorBody);
190
191impl Serialize for ErrorResponseBody {
192 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
193 where
194 S: serde::Serializer,
195 {
196 match &self.0 {
197 ErrorBody::Standard(standard_body) => standard_body.serialize(serializer),
198 ErrorBody::Json(json) => json.serialize(serializer),
199 ErrorBody::NotJson { .. } => {
200 Err(serde::ser::Error::custom("attempted to serialize ErrorBody::NotJson"))
201 }
202 }
203 }
204}
205
206#[derive(Debug, Error)]
209#[non_exhaustive]
210pub enum IntoHttpError {
211 #[error("failed to add authentication scheme: {0}")]
213 Authentication(Box<dyn std::error::Error + Send + Sync + 'static>),
214
215 #[error(
220 "endpoint was not supported by server-reported versions, \
221 but no unstable path to fall back to was defined"
222 )]
223 NoUnstablePath,
224
225 #[error(
228 "could not create any path variant for endpoint, as it was removed in version {}",
229 .0.as_str().expect("no endpoint was removed in Matrix 1.0")
230 )]
231 EndpointRemoved(MatrixVersion),
232
233 #[error("JSON serialization failed: {0}")]
235 Json(#[from] serde_json::Error),
236
237 #[error("query parameter serialization failed: {0}")]
239 Query(#[from] serde_html_form::ser::Error),
240
241 #[error("header serialization failed: {0}")]
243 Header(#[from] HeaderSerializationError),
244
245 #[error("HTTP request construction failed: {0}")]
247 Http(#[from] http::Error),
248}
249
250impl IntoHttpError {
251 pub fn authentication(
253 error: impl Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
254 ) -> Self {
255 Self::Authentication(error.into())
256 }
257}
258
259impl From<std::convert::Infallible> for IntoHttpError {
260 fn from(value: std::convert::Infallible) -> Self {
261 match value {}
262 }
263}
264
265impl From<http::header::InvalidHeaderValue> for IntoHttpError {
266 fn from(value: http::header::InvalidHeaderValue) -> Self {
267 Self::Header(value.into())
268 }
269}
270
271#[derive(Debug, Error)]
273#[non_exhaustive]
274pub enum FromHttpRequestError {
275 #[error("deserialization failed: {0}")]
277 Deserialization(DeserializationError),
278
279 #[error("http method mismatch: expected {expected}, received: {received}")]
281 MethodMismatch {
282 expected: http::method::Method,
284 received: http::method::Method,
286 },
287}
288
289impl<T> From<T> for FromHttpRequestError
290where
291 T: Into<DeserializationError>,
292{
293 fn from(err: T) -> Self {
294 Self::Deserialization(err.into())
295 }
296}
297
298#[derive(Debug)]
300#[non_exhaustive]
301pub enum FromHttpResponseError<E> {
302 Deserialization(DeserializationError),
304
305 Server(E),
307}
308
309impl<E> FromHttpResponseError<E> {
310 pub fn map<F>(self, f: impl FnOnce(E) -> F) -> FromHttpResponseError<F> {
313 match self {
314 Self::Deserialization(d) => FromHttpResponseError::Deserialization(d),
315 Self::Server(s) => FromHttpResponseError::Server(f(s)),
316 }
317 }
318}
319
320impl<E, F> FromHttpResponseError<Result<E, F>> {
321 pub fn transpose(self) -> Result<FromHttpResponseError<E>, F> {
323 match self {
324 Self::Deserialization(d) => Ok(FromHttpResponseError::Deserialization(d)),
325 Self::Server(s) => s.map(FromHttpResponseError::Server),
326 }
327 }
328}
329
330impl<E: fmt::Display> fmt::Display for FromHttpResponseError<E> {
331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 match self {
333 Self::Deserialization(err) => write!(f, "deserialization failed: {err}"),
334 Self::Server(err) => write!(f, "the server returned an error: {err}"),
335 }
336 }
337}
338
339impl<E, T> From<T> for FromHttpResponseError<E>
340where
341 T: Into<DeserializationError>,
342{
343 fn from(err: T) -> Self {
344 Self::Deserialization(err.into())
345 }
346}
347
348impl<E: StdError> StdError for FromHttpResponseError<E> {}
349
350pub trait FromHttpResponseErrorExt {
352 fn error_kind(&self) -> Option<&ErrorKind>;
355}
356
357impl FromHttpResponseErrorExt for FromHttpResponseError<Error> {
358 fn error_kind(&self) -> Option<&ErrorKind> {
359 as_variant!(self, Self::Server)?.error_kind()
360 }
361}
362
363#[derive(Debug, Error)]
366#[non_exhaustive]
367pub enum DeserializationError {
368 #[error(transparent)]
370 Utf8(#[from] std::str::Utf8Error),
371
372 #[error(transparent)]
374 Json(#[from] serde_json::Error),
375
376 #[error(transparent)]
378 Query(#[from] serde_html_form::de::Error),
379
380 #[error(transparent)]
382 Ident(#[from] crate::IdParseError),
383
384 #[error(transparent)]
386 Header(#[from] HeaderDeserializationError),
387
388 #[error(transparent)]
390 MultipartMixed(#[from] MultipartMixedDeserializationError),
391}
392
393impl From<std::convert::Infallible> for DeserializationError {
394 fn from(err: std::convert::Infallible) -> Self {
395 match err {}
396 }
397}
398
399impl From<http::header::ToStrError> for DeserializationError {
400 fn from(err: http::header::ToStrError) -> Self {
401 Self::Header(HeaderDeserializationError::ToStrError(err))
402 }
403}
404
405#[derive(Debug, Error)]
407#[non_exhaustive]
408pub enum HeaderDeserializationError {
409 #[error("{0}")]
411 ToStrError(#[from] http::header::ToStrError),
412
413 #[error("{0}")]
415 ParseIntError(#[from] ParseIntError),
416
417 #[error("failed to parse HTTP date")]
419 InvalidHttpDate,
420
421 #[error("missing header `{0}`")]
423 MissingHeader(String),
424
425 #[error("invalid header: {0}")]
427 InvalidHeader(Box<dyn std::error::Error + Send + Sync + 'static>),
428
429 #[error(
431 "The {header} header was received with an unexpected value, \
432 expected {expected}, received {unexpected}"
433 )]
434 InvalidHeaderValue {
435 header: String,
437 expected: String,
439 unexpected: String,
441 },
442
443 #[error(
446 "The `Content-Type` header for a `multipart/mixed` response is missing the `boundary` attribute"
447 )]
448 MissingMultipartBoundary,
449}
450
451#[derive(Debug, Error)]
453#[non_exhaustive]
454pub enum MultipartMixedDeserializationError {
455 #[error(
457 "multipart/mixed response does not have enough body parts, \
458 expected {expected}, found {found}"
459 )]
460 MissingBodyParts {
461 expected: usize,
463 found: usize,
465 },
466
467 #[error("multipart/mixed body part is missing separator between headers and content")]
469 MissingBodyPartInnerSeparator,
470
471 #[error("multipart/mixed body part header is missing separator between name and value")]
473 MissingHeaderSeparator,
474
475 #[error("invalid multipart/mixed header: {0}")]
477 InvalidHeader(Box<dyn std::error::Error + Send + Sync + 'static>),
478}
479
480#[derive(Debug)]
482#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
483pub struct UnknownVersionError;
484
485impl fmt::Display for UnknownVersionError {
486 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487 write!(f, "version string was unknown")
488 }
489}
490
491impl StdError for UnknownVersionError {}
492
493#[derive(Debug)]
498#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
499pub struct IncorrectArgumentCount {
500 pub expected: usize,
502
503 pub got: usize,
505}
506
507impl fmt::Display for IncorrectArgumentCount {
508 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509 write!(f, "incorrect path argument count, expected {}, got {}", self.expected, self.got)
510 }
511}
512
513impl StdError for IncorrectArgumentCount {}
514
515#[derive(Debug, Error)]
517#[non_exhaustive]
518pub enum HeaderSerializationError {
519 #[error(transparent)]
521 ToHeaderValue(#[from] http::header::InvalidHeaderValue),
522
523 #[error("invalid HTTP date")]
528 InvalidHttpDate,
529}