1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
use http::{HttpTryFrom, Response, StatusCode};
use http_service::Body;

use crate::response::IntoResponse;

pub(crate) type BoxTryFuture<T> = futures::future::FutureObj<'static, EndpointResult<T>>;

/// A convenient `Result` instantiation appropriate for most endpoints.
pub type EndpointResult<T = Response<Body>> = Result<T, Error>;

#[derive(Debug)]
pub struct StringError(pub String);
impl std::error::Error for StringError {}

impl std::fmt::Display for StringError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
        self.0.fmt(f)
    }
}

macro_rules! err_fmt {
    {$($t:tt)*} => {
        crate::error::StringError(format!($($t)*))
    }
}

/// A generic endpoint error, which can be converted into a response.
pub struct Error {
    resp: Response<Body>,
}

impl IntoResponse for Error {
    fn into_response(self) -> Response<Body> {
        self.resp
    }
}

struct Cause(Box<dyn std::error::Error + Send + Sync>);

impl From<Response<Body>> for Error {
    fn from(resp: Response<Body>) -> Error {
        Error { resp }
    }
}

impl From<StatusCode> for Error {
    fn from(status: StatusCode) -> Error {
        let resp = Response::builder()
            .status(status)
            .body(Body::empty())
            .unwrap();
        Error { resp }
    }
}

/// Extends the `Result` type with convenient methods for constructing Tide errors.
pub trait ResultExt<T>: Sized {
    /// Convert to an `EndpointResult`, treating the `Err` case as a client
    /// error (response code 400).
    fn client_err(self) -> EndpointResult<T> {
        self.with_err_status(400)
    }

    /// Convert to an `EndpointResult`, treating the `Err` case as a server
    /// error (response code 500).
    fn server_err(self) -> EndpointResult<T> {
        self.with_err_status(500)
    }

    /// Convert to an `EndpointResult`, wrapping the `Err` case with a custom
    /// response status.
    fn with_err_status<S>(self, status: S) -> EndpointResult<T>
    where
        StatusCode: HttpTryFrom<S>;
}

/// Extends the `Response` type with a method to extract error causes when applicable.
pub trait ResponseExt {
    /// Extract the cause of the unsuccessful response, if any
    fn err_cause(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)>;
}

impl<T> ResponseExt for Response<T> {
    fn err_cause(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
        self.extensions().get().map(|Cause(c)| &**c)
    }
}

impl<T, E: std::error::Error + Send + Sync + 'static> ResultExt<T> for std::result::Result<T, E> {
    fn with_err_status<S>(self, status: S) -> EndpointResult<T>
    where
        StatusCode: HttpTryFrom<S>,
    {
        self.map_err(|e| Error {
            resp: Response::builder()
                .status(status)
                .extension(Cause(Box::new(e)))
                .body(Body::empty())
                .unwrap(),
        })
    }
}