use crate::{
error::Error,
file::{ContentDisposition, DispositionType},
};
#[cfg(feature = "athene_body")]
use crate::body::{Body, ConvertBody};
#[cfg(not(feature = "athene_body"))]
use hyper::Body;
use cookie::{Cookie, CookieJar};
use headers::{Header, HeaderMapExt};
use hyper::http;
use hyper::http::{header::HeaderName, HeaderMap, HeaderValue, StatusCode, Version};
use serde::Serialize;
use std::{
any::Any,
convert::TryFrom,
ops::{Deref, DerefMut},
};
pub struct Response<T = Body> {
#[doc(hidden)]
inner: hyper::Response<T>,
#[doc(hidden)]
cookies: CookieJar,
}
impl<T> Response<T> {
#[inline]
pub fn builder() -> Builder {
Builder::new()
}
#[inline]
pub fn new(body: T) -> Self {
Response {
inner: hyper::Response::new(body),
cookies: Default::default(),
}
}
#[inline]
pub fn cookies(&self) -> &CookieJar {
&self.cookies
}
#[inline]
pub fn cookies_mut(&mut self) -> &mut CookieJar {
&mut self.cookies
}
#[inline]
pub fn map<F, U>(self, f: F) -> Response<U>
where
F: FnOnce(T) -> U,
{
let Response { inner, cookies } = self;
Response {
inner: inner.map(f),
cookies,
}
}
#[inline]
pub(crate) fn into_raw(self) -> Result<hyper::Response<T>, Error> {
let Response { mut inner, cookies } = self;
for c in cookies.iter() {
inner.headers_mut().append(
http::header::SET_COOKIE,
http::HeaderValue::from_str(c.to_string().as_str())?,
);
}
Ok(inner)
}
}
impl<T> Deref for Response<T> {
type Target = hyper::Response<T>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> DerefMut for Response<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[doc(hidden)]
pub struct Builder {
#[doc(hidden)]
inner: hyper::http::response::Builder,
#[doc(hidden)]
cookies: Option<CookieJar>,
#[doc(hidden)]
#[cfg(feature = "athene_body")]
body: Box<dyn ConvertBody + Send>,
#[cfg(not(feature = "athene_body"))]
body: Body,
#[doc(hidden)]
status_set: bool,
}
impl Builder {
#[inline]
pub fn new() -> Self {
Builder {
inner: hyper::http::response::Builder::new(),
cookies: None,
#[cfg(feature = "athene_body")]
body: Box::new(Option::<String>::None),
#[cfg(not(feature = "athene_body"))]
body: Body::empty(),
status_set: false,
}
}
#[inline]
pub(crate) fn status_if_not_set<T>(self, status: T) -> Builder
where
StatusCode: TryFrom<T>,
<StatusCode as TryFrom<T>>::Error: Into<http::Error>,
{
if !self.status_set {
self.status(status)
} else {
self
}
}
#[inline]
pub fn status<T>(mut self, status: T) -> Builder
where
StatusCode: TryFrom<T>,
<StatusCode as TryFrom<T>>::Error: Into<http::Error>,
{
self.status_set = true;
self.inner = self.inner.status(status);
self
}
#[inline]
pub fn redirect<T>(self, status: http::StatusCode, location: T) -> Self
where
http::HeaderValue: TryFrom<T>,
<http::HeaderValue as TryFrom<T>>::Error: Into<http::Error>,
{
self.status(status)
.header(http::header::LOCATION, location)
.body(hyper::Body::empty())
}
#[inline]
pub fn location(self, location: &'static str) -> Self {
self.header(
http::header::CONTENT_LOCATION,
http::header::HeaderValue::from_static(location),
)
.body(hyper::Body::empty())
}
#[inline]
pub fn version(mut self, version: Version) -> Builder {
self.inner = self.inner.version(version);
self
}
#[inline]
pub fn header_set<H: Header>(mut self, h: H) -> Self {
self.headers_mut().unwrap().typed_insert(h);
self
}
#[inline]
pub fn header<K, V>(mut self, key: K, value: V) -> Builder
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
{
self.inner = self.inner.header(key, value);
self
}
#[inline]
pub fn headers_ref(&self) -> Option<&HeaderMap<HeaderValue>> {
self.inner.headers_ref()
}
#[inline]
pub fn headers_mut(&mut self) -> Option<&mut HeaderMap<HeaderValue>> {
self.inner.headers_mut()
}
#[inline]
pub fn extension<T>(mut self, extension: T) -> Builder
where
T: Any + Send + Sync + 'static,
{
self.inner = self.inner.extension(extension);
self
}
#[inline]
pub fn cookie(mut self, cookie: Cookie<'static>) -> Builder {
self.cookies_mut().add(cookie);
self
}
#[inline]
pub fn cookies_mut(&mut self) -> &mut CookieJar {
if self.cookies.is_none() {
self.cookies = Some(CookieJar::new());
}
self.cookies.as_mut().expect("Checked above")
}
#[inline]
pub fn cookies(mut self, cookies: CookieJar) -> Builder {
self.cookies = Some(cookies);
self
}
#[inline]
#[cfg(feature = "athene_body")]
pub fn body<B: 'static + Into<hyper::Body> + Send>(mut self, body: B) -> Builder {
self.body = Box::new(Some(body));
self
}
#[inline]
#[cfg(not(feature = "athene_body"))]
pub fn body<B: 'static + Into<hyper::Body> + Send>(mut self, body: B) -> Builder {
self.body = body.into();
self
}
#[inline]
pub(crate) fn content_type_if_not_set(mut self, content_type: &str) -> Builder {
if let Some(headers) = self.inner.headers_mut() {
if !headers.contains_key(http::header::CONTENT_TYPE) {
if let Ok(hv) = HeaderValue::from_str(content_type) {
headers.insert(http::header::CONTENT_TYPE, hv);
}
}
}
self
}
#[inline]
pub fn with<T: Into<hyper::body::Bytes>>(self, content_type: &str, value: T) -> Self {
self.content_type_if_not_set(content_type)
.body(value.into())
}
#[inline]
pub fn html<T: Into<hyper::body::Bytes>>(self, value: T) -> Self {
self.content_type_if_not_set("text/html; charset=utf-8")
.body(value.into())
}
#[inline]
pub fn text<T: Into<hyper::body::Bytes>>(self, value: T) -> Self {
self.content_type_if_not_set("text/plain; charset=utf-8")
.body(value.into())
}
#[inline]
pub fn json<T: Serialize>(self, value: &T) -> Result<Self, (Self, Error)> {
match serde_json::to_vec(value) {
Ok(v) => Ok(self.content_type_if_not_set("application/json").body(v)),
Err(e) => Err((self, e.into())),
}
}
#[inline]
pub fn form<T: Serialize>(self, value: &T) -> Result<Self, (Self, Error)> {
match serde_urlencoded::to_string(value) {
Ok(v) => Ok(self
.content_type_if_not_set("application/x-www-form-urlencoded")
.body(v)),
Err(e) => Err((self, e.into())),
}
}
#[inline]
pub fn build(self) -> Result<Response<Body>, Error> {
let Builder {
inner,
cookies,
#[cfg(feature = "athene_body")]
mut body,
#[cfg(not(feature = "athene_body"))]
body,
..
} = self;
#[cfg(feature = "athene_body")]
let body = body.convert();
let raw = inner.body(body)?;
Ok(Response {
inner: raw,
cookies: cookies.unwrap_or_default(),
})
}
#[inline]
#[cfg(feature = "athene_body")]
pub fn write_file<P>(
&mut self,
path: P,
disposition_type: DispositionType,
) -> Result<&mut Self, Error>
where
P: Send + AsRef<std::path::Path>,
{
let path = path.as_ref();
let mut file = std::fs::File::open(path)?;
let mut buffer = Vec::new();
use std::io::Read;
file.read_to_end(&mut buffer)?;
if let Some(filename) = path.file_name() {
let name = filename.to_string_lossy();
let content_disposition = ContentDisposition::new(disposition_type, Some(&name));
if let Some(headers) = self.inner.headers_mut() {
headers.insert("content-disposition", content_disposition.try_into()?);
}
}
self.body = Box::new(Some(buffer));
Ok(self)
}
#[inline]
#[cfg(not(feature = "athene_body"))]
pub fn write_file<P>(
&mut self,
path: P,
disposition_type: DispositionType,
) -> Result<&mut Self, Error>
where
P: Send + AsRef<std::path::Path>,
{
let path = path.as_ref();
let mut file = std::fs::File::open(path)?;
let mut buffer = Vec::new();
use std::io::Read;
file.read_to_end(&mut buffer)?;
if let Some(filename) = path.file_name() {
let name = filename.to_string_lossy();
let content_disposition = ContentDisposition::new(disposition_type, Some(&name));
if let Some(headers) = self.inner.headers_mut() {
headers.insert("content-disposition", content_disposition.try_into()?);
}
}
self.body = buffer.into();
Ok(self)
}
#[cfg(feature = "static_file")]
#[inline]
pub fn reader(mut self, r: impl tokio::io::AsyncRead + Send + 'static) -> Self {
use tokio_util::io::ReaderStream;
let body = Body::wrap_stream(ReaderStream::new(r));
self.body = body;
self
}
#[cfg(feature = "static_file")]
#[inline]
pub async fn path(self, path: impl AsRef<std::path::Path>) -> Result<Self,Error> {
let target = path.as_ref();
let reader = tokio::fs::File::open(&target).await?;
let mime = mime_guess::from_path(&target).first_or_text_plain();
Ok(self.header_set(headers::ContentType::from(mime)).reader(reader))
}
}