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().status(self.status_code);
82
83 if let Some(ErrorKind::LimitExceeded(LimitExceededErrorData {
85 retry_after: Some(retry_after),
86 })) = self.error_kind()
87 {
88 let header_value = http::HeaderValue::try_from(retry_after)?;
89 builder = builder.header(http::header::RETRY_AFTER, header_value);
90 }
91
92 builder.body(ErrorResponseBody(self.body)).map_err(Into::into)
93 }
94}
95
96impl EndpointError for Error {
97 fn from_http_response(response: http::Response<&[u8]>) -> Self {
98 let status = response.status();
99
100 let body_bytes = response.body();
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 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#[derive(Debug, Clone)]
134#[allow(clippy::exhaustive_enums)]
135pub enum ErrorBody {
136 Standard(StandardErrorBody),
138
139 Json(JsonValue),
141
142 NotJson {
144 bytes: Bytes,
146
147 deserialization_error: Arc<serde_json::Error>,
149 },
150}
151
152impl ErrorBody {
153 pub fn into_error(self, status_code: http::StatusCode) -> Error {
157 Error { status_code, body: self }
158 }
159}
160
161#[derive(Clone, Debug, Deserialize, Serialize)]
163#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
164pub struct StandardErrorBody {
165 #[serde(flatten)]
167 pub kind: ErrorKind,
168
169 #[serde(rename = "error")]
171 pub message: String,
172}
173
174impl StandardErrorBody {
175 pub fn new(kind: ErrorKind, message: String) -> Self {
177 Self { kind, message }
178 }
179}
180
181#[doc(hidden)]
186#[derive(OutgoingBodyJson)]
187pub struct ErrorResponseBody(ErrorBody);
188
189impl Serialize for ErrorResponseBody {
190 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
191 where
192 S: serde::Serializer,
193 {
194 match &self.0 {
195 ErrorBody::Standard(standard_body) => standard_body.serialize(serializer),
196 ErrorBody::Json(json) => json.serialize(serializer),
197 ErrorBody::NotJson { .. } => {
198 Err(serde::ser::Error::custom("attempted to serialize ErrorBody::NotJson"))
199 }
200 }
201 }
202}
203
204#[derive(Debug, Error)]
207#[non_exhaustive]
208pub enum IntoHttpError {
209 #[error("failed to add authentication scheme: {0}")]
211 Authentication(Box<dyn std::error::Error + Send + Sync + 'static>),
212
213 #[error(
218 "endpoint was not supported by server-reported versions, \
219 but no unstable path to fall back to was defined"
220 )]
221 NoUnstablePath,
222
223 #[error(
226 "could not create any path variant for endpoint, as it was removed in version {}",
227 .0.as_str().expect("no endpoint was removed in Matrix 1.0")
228 )]
229 EndpointRemoved(MatrixVersion),
230
231 #[error("JSON serialization failed: {0}")]
233 Json(#[from] serde_json::Error),
234
235 #[error("query parameter serialization failed: {0}")]
237 Query(#[from] serde_html_form::ser::Error),
238
239 #[error("header serialization failed: {0}")]
241 Header(#[from] HeaderSerializationError),
242
243 #[error("HTTP request construction failed: {0}")]
245 Http(#[from] http::Error),
246}
247
248impl IntoHttpError {
249 pub fn authentication(
251 error: impl Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
252 ) -> Self {
253 Self::Authentication(error.into())
254 }
255}
256
257impl From<std::convert::Infallible> for IntoHttpError {
258 fn from(value: std::convert::Infallible) -> Self {
259 match value {}
260 }
261}
262
263impl From<http::header::InvalidHeaderValue> for IntoHttpError {
264 fn from(value: http::header::InvalidHeaderValue) -> Self {
265 Self::Header(value.into())
266 }
267}
268
269#[derive(Debug, Error)]
271#[non_exhaustive]
272pub enum FromHttpRequestError {
273 #[error("deserialization failed: {0}")]
275 Deserialization(DeserializationError),
276
277 #[error("http method mismatch: expected {expected}, received: {received}")]
279 MethodMismatch {
280 expected: http::method::Method,
282 received: http::method::Method,
284 },
285}
286
287impl<T> From<T> for FromHttpRequestError
288where
289 T: Into<DeserializationError>,
290{
291 fn from(err: T) -> Self {
292 Self::Deserialization(err.into())
293 }
294}
295
296#[derive(Debug)]
298#[non_exhaustive]
299pub enum FromHttpResponseError<E> {
300 Deserialization(DeserializationError),
302
303 Server(E),
305}
306
307impl<E> FromHttpResponseError<E> {
308 pub fn map<F>(self, f: impl FnOnce(E) -> F) -> FromHttpResponseError<F> {
311 match self {
312 Self::Deserialization(d) => FromHttpResponseError::Deserialization(d),
313 Self::Server(s) => FromHttpResponseError::Server(f(s)),
314 }
315 }
316}
317
318impl<E, F> FromHttpResponseError<Result<E, F>> {
319 pub fn transpose(self) -> Result<FromHttpResponseError<E>, F> {
321 match self {
322 Self::Deserialization(d) => Ok(FromHttpResponseError::Deserialization(d)),
323 Self::Server(s) => s.map(FromHttpResponseError::Server),
324 }
325 }
326}
327
328impl<E: fmt::Display> fmt::Display for FromHttpResponseError<E> {
329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330 match self {
331 Self::Deserialization(err) => write!(f, "deserialization failed: {err}"),
332 Self::Server(err) => write!(f, "the server returned an error: {err}"),
333 }
334 }
335}
336
337impl<E, T> From<T> for FromHttpResponseError<E>
338where
339 T: Into<DeserializationError>,
340{
341 fn from(err: T) -> Self {
342 Self::Deserialization(err.into())
343 }
344}
345
346impl<E: StdError> StdError for FromHttpResponseError<E> {}
347
348pub trait FromHttpResponseErrorExt {
350 fn error_kind(&self) -> Option<&ErrorKind>;
353}
354
355impl FromHttpResponseErrorExt for FromHttpResponseError<Error> {
356 fn error_kind(&self) -> Option<&ErrorKind> {
357 as_variant!(self, Self::Server)?.error_kind()
358 }
359}
360
361#[derive(Debug, Error)]
364#[non_exhaustive]
365pub enum DeserializationError {
366 #[error(transparent)]
368 Utf8(#[from] std::str::Utf8Error),
369
370 #[error(transparent)]
372 Json(#[from] serde_json::Error),
373
374 #[error(transparent)]
376 Query(#[from] serde_html_form::de::Error),
377
378 #[error(transparent)]
380 Ident(#[from] crate::IdParseError),
381
382 #[error(transparent)]
384 Header(#[from] HeaderDeserializationError),
385
386 #[error(transparent)]
388 MultipartMixed(#[from] MultipartMixedDeserializationError),
389}
390
391impl From<std::convert::Infallible> for DeserializationError {
392 fn from(err: std::convert::Infallible) -> Self {
393 match err {}
394 }
395}
396
397impl From<http::header::ToStrError> for DeserializationError {
398 fn from(err: http::header::ToStrError) -> Self {
399 Self::Header(HeaderDeserializationError::ToStrError(err))
400 }
401}
402
403#[derive(Debug, Error)]
405#[non_exhaustive]
406pub enum HeaderDeserializationError {
407 #[error("{0}")]
409 ToStrError(#[from] http::header::ToStrError),
410
411 #[error("{0}")]
413 ParseIntError(#[from] ParseIntError),
414
415 #[error("failed to parse HTTP date")]
417 InvalidHttpDate,
418
419 #[error("missing header `{0}`")]
421 MissingHeader(String),
422
423 #[error("invalid header: {0}")]
425 InvalidHeader(Box<dyn std::error::Error + Send + Sync + 'static>),
426
427 #[error(
429 "The {header} header was received with an unexpected value, \
430 expected {expected}, received {unexpected}"
431 )]
432 InvalidHeaderValue {
433 header: String,
435 expected: String,
437 unexpected: String,
439 },
440
441 #[error(
444 "The `Content-Type` header for a `multipart/mixed` response is missing the `boundary` attribute"
445 )]
446 MissingMultipartBoundary,
447}
448
449#[derive(Debug, Error)]
451#[non_exhaustive]
452pub enum MultipartMixedDeserializationError {
453 #[error(
455 "multipart/mixed response does not have enough body parts, \
456 expected {expected}, found {found}"
457 )]
458 MissingBodyParts {
459 expected: usize,
461 found: usize,
463 },
464
465 #[error("multipart/mixed body part is missing separator between headers and content")]
467 MissingBodyPartInnerSeparator,
468
469 #[error("multipart/mixed body part header is missing separator between name and value")]
471 MissingHeaderSeparator,
472
473 #[error("invalid multipart/mixed header: {0}")]
475 InvalidHeader(Box<dyn std::error::Error + Send + Sync + 'static>),
476}
477
478#[derive(Debug)]
480#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
481pub struct UnknownVersionError;
482
483impl fmt::Display for UnknownVersionError {
484 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
485 write!(f, "version string was unknown")
486 }
487}
488
489impl StdError for UnknownVersionError {}
490
491#[derive(Debug)]
496#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
497pub struct IncorrectArgumentCount {
498 pub expected: usize,
500
501 pub got: usize,
503}
504
505impl fmt::Display for IncorrectArgumentCount {
506 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
507 write!(f, "incorrect path argument count, expected {}, got {}", self.expected, self.got)
508 }
509}
510
511impl StdError for IncorrectArgumentCount {}
512
513#[derive(Debug, Error)]
515#[non_exhaustive]
516pub enum HeaderSerializationError {
517 #[error(transparent)]
519 ToHeaderValue(#[from] http::header::InvalidHeaderValue),
520
521 #[error("invalid HTTP date")]
526 InvalidHttpDate,
527}