use std::{
any::Any,
convert::TryFrom,
ops::{Deref, DerefMut},
};
use crate::cookie::{Cookie, CookieJar};
use http::{header::HeaderName, response::Builder as RawBuilder, HeaderMap, HeaderValue, Response as RawResponse, StatusCode, Version};
use hyper::body::Body as RawBody;
use crate::{
body::{Body, TransmuteBody},
error::SaphirError,
};
pub struct Response<T = Body> {
#[doc(hidden)]
inner: RawResponse<T>,
#[doc(hidden)]
cookies: CookieJar,
#[cfg(feature = "tracing-instrument")]
#[doc(hidden)]
pub(crate) span: Option<tracing::span::Span>,
}
impl<T> Response<T> {
pub fn builder() -> Builder {
Builder::new()
}
pub fn new(body: T) -> Self {
Response {
inner: RawResponse::new(body),
cookies: Default::default(),
#[cfg(feature = "tracing-instrument")]
span: None,
}
}
pub fn cookies(&self) -> &CookieJar {
&self.cookies
}
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,
{
#[cfg(feature = "tracing-instrument")]
{
let Response { inner, cookies, span } = self;
Response {
inner: inner.map(f),
cookies,
span,
}
}
#[cfg(not(feature = "tracing-instrument"))]
{
let Response { inner, cookies } = self;
Response { inner: inner.map(f), cookies }
}
}
pub(crate) fn into_raw(self) -> Result<RawResponse<T>, SaphirError> {
#[cfg(feature = "tracing-instrument")]
let Response { mut inner, cookies, span: _ } = self;
#[cfg(not(feature = "tracing-instrument"))]
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 = RawResponse<T>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> DerefMut for Response<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
pub struct Builder {
#[doc(hidden)]
inner: RawBuilder,
#[doc(hidden)]
cookies: Option<CookieJar>,
#[doc(hidden)]
body: Box<dyn TransmuteBody + Send>,
#[doc(hidden)]
status_set: bool,
#[cfg(feature = "tracing-instrument")]
#[doc(hidden)]
span: Option<tracing::span::Span>,
}
impl Builder {
#[inline]
pub fn new() -> Self {
Builder {
inner: RawBuilder::new(),
cookies: None,
body: Box::new(Option::<String>::None),
status_set: false,
#[cfg(feature = "tracing-instrument")]
span: None,
}
}
#[cfg(feature = "tracing-instrument")]
#[inline]
pub(crate) fn span(mut self, span: tracing::span::Span) -> Builder {
self.span = Some(span);
self
}
#[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 version(mut self, version: Version) -> Builder {
self.inner = self.inner.version(version);
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]
pub fn body<B: 'static + Into<RawBody> + Send>(mut self, body: B) -> Builder {
self.body = Box::new(Some(body));
self
}
#[cfg(any(feature = "form", feature = "json"))]
#[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 build(self) -> Result<Response<Body>, SaphirError> {
#[cfg(feature = "tracing-instrument")]
let Builder {
inner,
cookies,
mut body,
span,
..
} = self;
#[cfg(not(feature = "tracing-instrument"))]
let Builder { inner, cookies, mut body, .. } = self;
let b = body.transmute();
let raw = inner.body(b)?;
Ok(Response {
inner: raw,
cookies: cookies.unwrap_or_default(),
#[cfg(feature = "tracing-instrument")]
span,
})
}
}
impl Default for Builder {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
mod json {
use serde::Serialize;
use super::*;
impl Builder {
pub fn json<T: Serialize>(self, t: &T) -> Result<Builder, (Builder, SaphirError)> {
match serde_json::to_vec(t) {
Ok(v) => Ok(self.content_type_if_not_set("application/json").body(v)),
Err(e) => Err((self, e.into())),
}
}
}
}
#[cfg(feature = "form")]
#[cfg_attr(docsrs, doc(cfg(feature = "form")))]
mod form {
use serde::Serialize;
use super::*;
impl Builder {
pub fn form<T: Serialize>(self, t: &T) -> Result<Builder, (Builder, SaphirError)> {
match serde_urlencoded::to_string(t) {
Ok(v) => Ok(self.content_type_if_not_set("application/x-www-form-urlencoded").body(v)),
Err(e) => Err((self, e.into())),
}
}
}
}
#[cfg(feature = "file")]
#[cfg_attr(docsrs, doc(cfg(feature = "file")))]
mod file {
use super::*;
use crate::{file::FileStream, prelude::Bytes};
use futures::Stream;
impl Builder {
pub fn file<F: Into<FileStream>>(self, file: F) -> Builder {
self.body(Box::new(file.into())
as Box<
dyn Stream<Item = Result<Bytes, Box<dyn std::error::Error + Send + Sync + 'static>>> + Send + 'static,
>)
}
}
}