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 super::{EndpointError, MatrixVersion, OutgoingResponse};
#[allow(clippy::exhaustive_structs)]
#[derive(Clone, Debug)]
pub struct MatrixError {
pub status_code: http::StatusCode,
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<T: Default + BufMut>(
self,
) -> Result<http::Response<T>, IntoHttpError> {
http::Response::builder()
.header(http::header::CONTENT_TYPE, "application/json")
.status(self.status_code)
.body(crate::serde::json_to_buf(&self.body)?)
.map_err(Into::into)
}
}
impl EndpointError for MatrixError {
fn try_from_http_response<T: AsRef<[u8]>>(
response: http::Response<T>,
) -> Result<Self, DeserializationError> {
Ok(Self {
status_code: response.status(),
body: from_json_slice(response.body().as_ref())?,
})
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum IntoHttpError {
#[error("no access token given, but this endpoint requires one")]
NeedsAuthentication,
#[error(
"endpoint was not supported by server-reported versions, \
but no unstable path to fall back to was defined"
)]
NoUnstablePath,
#[error("could not create any path variant for endpoint, as it was removed in version {0}")]
EndpointRemoved(MatrixVersion),
#[error("JSON serialization failed: {0}")]
Json(#[from] serde_json::Error),
#[error("query parameter serialization failed: {0}")]
Query(#[from] crate::serde::urlencoded::ser::Error),
#[error("header serialization failed: {0}")]
Header(#[from] http::header::InvalidHeaderValue),
#[error("HTTP request construction failed: {0}")]
Http(#[from] http::Error),
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum FromHttpRequestError {
#[error("deserialization failed: {0}")]
Deserialization(DeserializationError),
#[error("http method mismatch: expected {expected}, received: {received}")]
MethodMismatch {
expected: http::method::Method,
received: http::method::Method,
},
}
impl<T> From<T> for FromHttpRequestError
where
T: Into<DeserializationError>,
{
fn from(err: T) -> Self {
Self::Deserialization(err.into())
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum FromHttpResponseError<E> {
Deserialization(DeserializationError),
Server(ServerError<E>),
}
impl<E> FromHttpResponseError<E> {
pub fn map<F>(
self,
f: impl FnOnce(ServerError<E>) -> ServerError<F>,
) -> FromHttpResponseError<F> {
match self {
Self::Deserialization(d) => FromHttpResponseError::Deserialization(d),
Self::Server(s) => FromHttpResponseError::Server(f(s)),
}
}
}
impl<E, F> FromHttpResponseError<Result<E, F>> {
pub fn transpose(self) -> Result<FromHttpResponseError<E>, F> {
match self {
Self::Deserialization(d) => Ok(FromHttpResponseError::Deserialization(d)),
Self::Server(s) => s.transpose().map(FromHttpResponseError::Server),
}
}
}
impl<E: fmt::Display> fmt::Display for FromHttpResponseError<E> {
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<E> From<ServerError<E>> for FromHttpResponseError<E> {
fn from(err: ServerError<E>) -> Self {
Self::Server(err)
}
}
impl<E, T> From<T> for FromHttpResponseError<E>
where
T: Into<DeserializationError>,
{
fn from(err: T) -> Self {
Self::Deserialization(err.into())
}
}
impl<E: StdError> StdError for FromHttpResponseError<E> {}
#[derive(Debug)]
#[allow(clippy::exhaustive_enums)]
pub enum ServerError<E> {
Known(E),
Unknown(DeserializationError),
}
impl<E> ServerError<E> {
pub fn map<F>(self, f: impl FnOnce(E) -> F) -> ServerError<F> {
match self {
Self::Known(k) => ServerError::Known(f(k)),
Self::Unknown(u) => ServerError::Unknown(u),
}
}
}
impl<E, F> ServerError<Result<E, F>> {
pub fn transpose(self) -> Result<ServerError<E>, F> {
match self {
Self::Known(Ok(k)) => Ok(ServerError::Known(k)),
Self::Known(Err(e)) => Err(e),
Self::Unknown(u) => Ok(ServerError::Unknown(u)),
}
}
}
impl<E: fmt::Display> fmt::Display for ServerError<E> {
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<E: StdError> StdError for ServerError<E> {}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum DeserializationError {
#[error(transparent)]
Utf8(#[from] std::str::Utf8Error),
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error(transparent)]
Query(#[from] crate::serde::urlencoded::de::Error),
#[error(transparent)]
Ident(#[from] crate::IdParseError),
#[error(transparent)]
Header(#[from] HeaderDeserializationError),
}
impl From<std::convert::Infallible> for DeserializationError {
fn from(err: std::convert::Infallible) -> Self {
match err {}
}
}
impl From<http::header::ToStrError> for DeserializationError {
fn from(err: http::header::ToStrError) -> Self {
Self::Header(HeaderDeserializationError::ToStrError(err))
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum HeaderDeserializationError {
#[error("{0}")]
ToStrError(http::header::ToStrError),
#[error("missing header `{0}`")]
MissingHeader(String),
}
#[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 {}