use crate::body::Body;
use crate::error::Error;
use bytes::Bytes;
use http::header::{HeaderName, CONTENT_TYPE};
use http::{HeaderMap, HeaderValue, StatusCode};
#[derive(Debug)]
pub struct Response {
pub status: StatusCode,
pub headers: HeaderMap,
pub body: Body,
}
impl Response {
pub fn new(status: StatusCode) -> Self {
Self {
status,
headers: HeaderMap::new(),
body: Body::empty(),
}
}
pub fn text(body: impl Into<String>) -> Self {
let mut r = Self::new(StatusCode::OK);
r.headers.insert(
CONTENT_TYPE,
HeaderValue::from_static("text/plain; charset=utf-8"),
);
r.body = Body::from(body.into());
r
}
pub fn bytes(content_type: &'static str, body: impl Into<Bytes>) -> Self {
let mut r = Self::new(StatusCode::OK);
r.headers
.insert(CONTENT_TYPE, HeaderValue::from_static(content_type));
r.body = Body::from(body.into());
r
}
pub fn stream(content_type: &'static str, body: Body) -> Self {
let mut r = Self::new(StatusCode::OK);
r.headers
.insert(CONTENT_TYPE, HeaderValue::from_static(content_type));
r.body = body;
r
}
pub fn with_status(mut self, status: StatusCode) -> Self {
self.status = status;
self
}
pub fn with_cookie(mut self, cookie: crate::cookie::Cookie) -> Self {
if let Ok(v) = HeaderValue::from_str(&cookie.to_header_value()) {
self.headers.append(http::header::SET_COOKIE, v);
}
self
}
pub fn with_header(mut self, name: HeaderName, value: HeaderValue) -> Self {
self.headers.insert(name, value);
self
}
}
pub trait IntoResponse {
fn into_response(self) -> Response;
}
impl IntoResponse for Response {
fn into_response(self) -> Response {
self
}
}
impl IntoResponse for () {
fn into_response(self) -> Response {
Response::new(StatusCode::OK)
}
}
impl IntoResponse for &'static str {
fn into_response(self) -> Response {
Response::text(self)
}
}
impl IntoResponse for String {
fn into_response(self) -> Response {
Response::text(self)
}
}
impl IntoResponse for StatusCode {
fn into_response(self) -> Response {
Response::new(self)
}
}
impl<T: IntoResponse> IntoResponse for (StatusCode, T) {
fn into_response(self) -> Response {
let (status, inner) = self;
inner.into_response().with_status(status)
}
}
impl IntoResponse for Error {
fn into_response(self) -> Response {
let mut res = Response::text(self.message().to_string()).with_status(self.status());
for (name, value) in self.response_headers() {
res.headers.insert(name.clone(), value.clone());
}
res
}
}
impl<T: IntoResponse> IntoResponse for crate::error::Result<T> {
fn into_response(self) -> Response {
match self {
Ok(v) => v.into_response(),
Err(e) => e.into_response(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn text_sets_content_type_and_body() {
let r = Response::text("hi");
assert_eq!(r.status, StatusCode::OK);
assert_eq!(r.body, Bytes::from("hi"));
assert_eq!(
r.headers.get(CONTENT_TYPE).unwrap(),
"text/plain; charset=utf-8"
);
}
#[test]
fn status_tuple_overrides_status() {
let r = (StatusCode::CREATED, "made").into_response();
assert_eq!(r.status, StatusCode::CREATED);
assert_eq!(r.body, Bytes::from("made"));
}
#[test]
fn error_renders_with_its_status() {
let r = Error::bad_request("x").into_response();
assert_eq!(r.status, StatusCode::BAD_REQUEST);
}
}