//! This module contains types for all kinds of errors that can occur when //! converting between http requests / responses and ruma's representation of //! matrix API requests / responses. use std::{error::Error as StdError, fmt, sync::Arc}; use bytes::{BufMut, Bytes}; use serde_json::{from_slice as from_json_slice, Value as JsonValue}; use thiserror::Error; use super::{EndpointError, MatrixVersion, OutgoingResponse}; /// A general-purpose Matrix error type consisting of an HTTP status code and a JSON body. /// /// Note that individual `ruma-*-api` crates may provide more specific error types. #[allow(clippy::exhaustive_structs)] #[derive(Clone, Debug)] pub struct MatrixError { /// The http response's status code. pub status_code: http::StatusCode, /// The http response's body. pub body: MatrixErrorBody, } impl fmt::Display for MatrixError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let status_code = self.status_code.as_u16(); match &self.body { MatrixErrorBody::Json(json) => write!(f, "[{status_code}] {json}"), MatrixErrorBody::NotJson { .. } => write!(f, "[{status_code}] "), } } } impl StdError for MatrixError {} impl OutgoingResponse for MatrixError { fn try_into_http_response( self, ) -> Result, IntoHttpError> { http::Response::builder() .header(http::header::CONTENT_TYPE, "application/json") .status(self.status_code) .body(match self.body { MatrixErrorBody::Json(json) => crate::serde::json_to_buf(&json)?, MatrixErrorBody::NotJson { .. } => { return Err(IntoHttpError::Json(serde::ser::Error::custom( "attempted to serialize MatrixErrorBody::NotJson", ))); } }) .map_err(Into::into) } } impl EndpointError for MatrixError { fn from_http_response>(response: http::Response) -> Self { let status_code = response.status(); let body = MatrixErrorBody::from_bytes(response.body().as_ref()); Self { status_code, body } } } /// The body of an error response. #[derive(Clone, Debug)] #[allow(clippy::exhaustive_enums)] pub enum MatrixErrorBody { /// A JSON body, as intended. Json(JsonValue), /// A response body that is not valid JSON. #[non_exhaustive] NotJson { /// The raw bytes of the response body. bytes: Bytes, /// The error from trying to deserialize the bytes as JSON. deserialization_error: Arc, }, } impl MatrixErrorBody { /// Create a `MatrixErrorBody` from the given HTTP body bytes. pub fn from_bytes(body_bytes: &[u8]) -> Self { match from_json_slice(body_bytes) { Ok(json) => MatrixErrorBody::Json(json), Err(e) => MatrixErrorBody::NotJson { bytes: Bytes::copy_from_slice(body_bytes), deserialization_error: Arc::new(e), }, } } } /// An error when converting one of ruma's endpoint-specific request or response /// types to the corresponding http type. #[derive(Debug, Error)] #[non_exhaustive] pub enum IntoHttpError { /// Tried to create an authentication request without an access token. #[error("no access token given, but this endpoint requires one")] NeedsAuthentication, /// Tried to create a request with an old enough version, for which no unstable endpoint /// exists. /// /// This is also a fallback error for if the version is too new for this endpoint. #[error( "endpoint was not supported by server-reported versions, \ but no unstable path to fall back to was defined" )] NoUnstablePath, /// Tried to create a request with [`MatrixVersion`]s for all of which this endpoint was /// removed. #[error("could not create any path variant for endpoint, as it was removed in version {0}")] EndpointRemoved(MatrixVersion), /// JSON serialization failed. #[error("JSON serialization failed: {0}")] Json(#[from] serde_json::Error), /// Query parameter serialization failed. #[error("query parameter serialization failed: {0}")] Query(#[from] serde_html_form::ser::Error), /// Header serialization failed. #[error("header serialization failed: {0}")] Header(#[from] http::header::InvalidHeaderValue), /// HTTP request construction failed. #[error("HTTP request construction failed: {0}")] Http(#[from] http::Error), } /// An error when converting a http request to one of ruma's endpoint-specific request types. #[derive(Debug, Error)] #[non_exhaustive] pub enum FromHttpRequestError { /// Deserialization failed #[error("deserialization failed: {0}")] Deserialization(DeserializationError), /// HTTP method mismatch #[error("http method mismatch: expected {expected}, received: {received}")] MethodMismatch { /// expected http method expected: http::method::Method, /// received http method received: http::method::Method, }, } impl From for FromHttpRequestError where T: Into, { fn from(err: T) -> Self { Self::Deserialization(err.into()) } } /// An error when converting a http response to one of Ruma's endpoint-specific response types. #[derive(Debug)] #[non_exhaustive] pub enum FromHttpResponseError { /// Deserialization failed Deserialization(DeserializationError), /// The server returned a non-success status Server(E), } impl FromHttpResponseError { /// Map `FromHttpResponseError` to `FromHttpResponseError` by applying a function to a /// contained `Server` value, leaving a `Deserialization` value untouched. pub fn map(self, f: impl FnOnce(E) -> F) -> FromHttpResponseError { match self { Self::Deserialization(d) => FromHttpResponseError::Deserialization(d), Self::Server(s) => FromHttpResponseError::Server(f(s)), } } } impl FromHttpResponseError> { /// Transpose `FromHttpResponseError>` to `Result, F>`. pub fn transpose(self) -> Result, F> { match self { Self::Deserialization(d) => Ok(FromHttpResponseError::Deserialization(d)), Self::Server(s) => s.map(FromHttpResponseError::Server), } } } impl fmt::Display for FromHttpResponseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Deserialization(err) => write!(f, "deserialization failed: {err}"), Self::Server(err) => write!(f, "the server returned an error: {err}"), } } } impl From for FromHttpResponseError where T: Into, { fn from(err: T) -> Self { Self::Deserialization(err.into()) } } impl StdError for FromHttpResponseError {} /// An error when converting a http request / response to one of ruma's endpoint-specific request / /// response types. #[derive(Debug, Error)] #[non_exhaustive] pub enum DeserializationError { /// Encountered invalid UTF-8. #[error(transparent)] Utf8(#[from] std::str::Utf8Error), /// JSON deserialization failed. #[error(transparent)] Json(#[from] serde_json::Error), /// Query parameter deserialization failed. #[error(transparent)] Query(#[from] serde_html_form::de::Error), /// Got an invalid identifier. #[error(transparent)] Ident(#[from] crate::IdParseError), /// Header value deserialization failed. #[error(transparent)] Header(#[from] HeaderDeserializationError), } impl From for DeserializationError { fn from(err: std::convert::Infallible) -> Self { match err {} } } impl From for DeserializationError { fn from(err: http::header::ToStrError) -> Self { Self::Header(HeaderDeserializationError::ToStrError(err)) } } /// An error with the http headers. #[derive(Debug, Error)] #[non_exhaustive] pub enum HeaderDeserializationError { /// Failed to convert `http::header::HeaderValue` to `str`. #[error("{0}")] ToStrError(http::header::ToStrError), /// The given required header is missing. #[error("missing header `{0}`")] MissingHeader(String), } /// An error that happens when Ruma cannot understand a Matrix version. #[derive(Debug)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct UnknownVersionError; impl fmt::Display for UnknownVersionError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "version string was unknown") } } impl StdError for UnknownVersionError {} /// An error that happens when an incorrect amount of arguments have been passed to PathData parts /// formatting. #[derive(Debug)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct IncorrectArgumentCount { /// The expected amount of arguments. pub expected: usize, /// The amount of arguments received. pub got: usize, } impl fmt::Display for IncorrectArgumentCount { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "incorrect path argument count, expected {}, got {}", self.expected, self.got) } } impl StdError for IncorrectArgumentCount {}