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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use crate::body;
use crate::body::Body;

/// An HTTP response.
///
/// A convenient alias for the `http::Response` type, using Tide's `Body`.
pub type Response = http::Response<Body>;

/// A value that is synchronously convertable into a `Response`.
pub trait IntoResponse: Send + Sized {
    /// Convert the value into a `Response`.
    fn into_response(self) -> Response;

    /// Create a new `IntoResponse` value that will respond with the given status code.
    ///
    /// ```
    /// # use tide::IntoResponse;
    /// let resp = "Hello, 404!".with_status(http::status::StatusCode::NOT_FOUND).into_response();
    /// assert_eq!(resp.status(), http::status::StatusCode::NOT_FOUND);
    /// ```
    fn with_status(self, status: http::status::StatusCode) -> WithStatus<Self> {
        WithStatus {
            inner: self,
            status,
        }
    }
}

impl IntoResponse for () {
    fn into_response(self) -> Response {
        http::Response::builder()
            .status(http::status::StatusCode::OK)
            .body(Body::empty())
            .unwrap()
    }
}

impl IntoResponse for Vec<u8> {
    fn into_response(self) -> Response {
        http::Response::builder()
            .status(http::status::StatusCode::OK)
            .header("Content-Type", "text/plain; charset=utf-8")
            .body(Body::from(self))
            .unwrap()
    }
}

impl IntoResponse for body::Bytes {
    fn into_response(self) -> Response {
        self.to_vec().into_response()
    }
}

impl IntoResponse for String {
    fn into_response(self) -> Response {
        self.into_bytes().into_response()
    }
}

impl IntoResponse for &'_ str {
    fn into_response(self) -> Response {
        self.to_string().into_response()
    }
}

impl IntoResponse for http::status::StatusCode {
    fn into_response(self) -> Response {
        http::Response::builder()
            .status(self)
            .body(Body::empty())
            .unwrap()
    }
}

impl<T: IntoResponse, U: IntoResponse> IntoResponse for Result<T, U> {
    fn into_response(self) -> Response {
        match self {
            Ok(r) => r.into_response(),
            Err(r) => {
                let res = r.into_response();
                if res.status().is_success() {
                    panic!(
                        "Attempted to yield error response with success code {:?}",
                        res.status()
                    )
                }
                res
            }
        }
    }
}

impl<T: Send + Into<Body>> IntoResponse for http::Response<T> {
    fn into_response(self) -> Response {
        self.map(Into::into)
    }
}

/// A response type that modifies the status code.
pub struct WithStatus<R> {
    inner: R,
    status: http::status::StatusCode,
}

impl<R: IntoResponse> IntoResponse for WithStatus<R> {
    fn into_response(self) -> Response {
        let mut resp = self.inner.into_response();
        *resp.status_mut() = self.status;
        resp
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_status() {
        let resp = "foo"
            .with_status(http::status::StatusCode::NOT_FOUND)
            .into_response();
        assert_eq!(resp.status(), http::status::StatusCode::NOT_FOUND);
    }
}