//! 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}; use bytes::BufMut; use serde_json::{from_slice as from_json_slice, Value as JsonValue}; use thiserror::Error; use crate::{EndpointError, 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: JsonValue, } impl fmt::Display for MatrixError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "[{}] ", self.status_code.as_u16())?; fmt::Display::fmt(&self.body, f) } } 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(ruma_serde::json_to_buf(&self.body)?) .map_err(Into::into) } } impl EndpointError for MatrixError { fn try_from_http_response>( response: http::Response, ) -> Result { Ok(Self { status_code: response.status(), body: from_json_slice(response.body().as_ref())?, }) } } /// 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( "This endpoint has to be converted to http::Request using \ try_into_authenticated_http_request" )] NeedsAuthentication, /// 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] ruma_serde::urlencoded::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 Http(ServerError), } 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::Http(err) => write!(f, "the server returned an error: {}", err), } } } impl From> for FromHttpResponseError { fn from(err: ServerError) -> Self { Self::Http(err) } } impl From for FromHttpResponseError where T: Into, { fn from(err: T) -> Self { Self::Deserialization(err.into()) } } impl StdError for FromHttpResponseError {} /// An error was reported by the server (HTTP status code 4xx or 5xx) #[derive(Debug)] #[allow(clippy::exhaustive_enums)] pub enum ServerError { /// An error that is expected to happen under certain circumstances and /// that has a well-defined structure Known(E), /// An error of unexpected type of structure Unknown(DeserializationError), } impl fmt::Display for ServerError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { ServerError::Known(e) => fmt::Display::fmt(e, f), ServerError::Unknown(res_err) => fmt::Display::fmt(res_err, f), } } } impl StdError for ServerError {} /// 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("{0}")] Utf8(#[from] std::str::Utf8Error), /// JSON deserialization failed. #[error("{0}")] Json(#[from] serde_json::Error), /// Query parameter deserialization failed. #[error("{0}")] Query(#[from] ruma_serde::urlencoded::de::Error), /// Got an invalid identifier. #[error("{0}")] Ident(#[from] ruma_identifiers::Error), /// Header value deserialization failed. #[error("{0}")] 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), }