athene 2.0.4

A simple and lightweight rust web framework based on hyper
Documentation
#[cfg(feature = "download")]
use crate::download::{ContentDisposition, DispositionType};
use crate::{error::Error,body::HttpBody};
use headers::{Header, HeaderMapExt};
use hyper::{
    body::Bytes,
    http::{self, HeaderValue},
    StatusCode,
};
use serde::Serialize;
use std::ops::{Deref,DerefMut};

pub type Response<T = HttpBody> = hyper::http::Response<T>;

#[derive(Debug)]
pub struct Builder {
    #[doc(hidden)]
    pub inner: hyper::http::response::Builder,
    #[doc(hidden)]
    pub body: HttpBody,
    #[doc(hidden)]
    status_set: bool,
}

impl Builder {

    #[inline]
    pub fn new() -> Self {
        Builder {
            inner: hyper::http::response::Builder::new(),
            body: HttpBody::Empty,
            status_set: false,
        }
    }

    /// Add cookie
    #[cfg(feature = "cookie")]
    #[inline]
    pub fn cookie(mut self, cookie: cookie::Cookie<'static>) -> Self {
        self.inner.headers_mut().map(|h|h.append(
            http::header::SET_COOKIE,
            http::HeaderValue::from_str(cookie.to_string().as_str()).expect("Invalid Cookie"),
        ));
        self
    }

    #[inline]
    pub fn header_set<H: Header>(mut self, h: H) -> Self {
        self.inner.headers_mut().map(|header| header.typed_insert(h));
        self
    }

    #[inline]
    pub(crate) fn status_if_not_set<T>(self, status: T) -> Self
    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) -> Self
    where
        StatusCode: TryFrom<T>,
        <StatusCode as TryFrom<T>>::Error: Into<http::Error>,
    {
        self.status_set = true;
        self.inner = self.inner.status(status);
        self
    }

    /// The response redirects to the specified URL.
    /// 
    /// [mdn]: <https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect>
    #[inline]
    pub fn redirect(mut self, status: http::StatusCode, url: impl AsRef<str>) -> Self {
        let value = http::header::HeaderValue::try_from(url.as_ref())
            .expect("url is not the correct value");
        self.status_set = true;
        self.inner = self.inner.status(status).header(http::header::LOCATION, value);
        self
    }

    /// The [`Content-Location`][mdn] header indicates an alternate location for the returned data.
    /// 
    /// [mdn]: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Location>
    #[inline]
    pub fn location(mut self, location: impl AsRef<str>) -> Self {
        let value = http::header::HeaderValue::try_from(location.as_ref())
            .expect("location is not the correct value");
        self.inner = self.inner.header(http::header::CONTENT_LOCATION, value);
        self
    }

    /// Write bytes data to body. If body is none, a new `Body` will created.
    #[inline]
    pub fn body(mut self, data: impl Into<Bytes>) -> Self {
        let body: Bytes = data.into();
        let body: HttpBody = body.into();
        self.body = body;
        self
    }

    /// The response with the specified [`Content-Type`][mdn].
    /// 
    /// [mdn]: <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type>
    #[inline]
    pub fn with<T: Into<Bytes>>(mut self, content_type: &str, value: T) -> Self {
        if let Some(headers) = self.inner.headers_mut() {
            if let Ok(c_type) = HeaderValue::from_str(content_type) {
                headers.insert("content-type", c_type);
            }
        };
        self.body(value)
    }

    /// The response with `text/html; charset=utf-8` media type.
    #[inline]
    pub fn html<T: Into<Bytes>>(self, value: T) -> Self {
        self.with("text/html; charset=utf-8", value)
    }

    /// The response with `text/plain; charset=utf-8` media type.
    #[inline]
    pub fn text<T: Into<Bytes>>(self, value: T) -> Self {
        self.with("text/plain; charset=utf-8", value)
    }

    /// The response with `application/json; charset=utf-8` media type.
    #[inline]
    pub fn json<T: Serialize>(mut self, value: &T) -> Self {
        match serde_json::to_vec(value) {
            Ok(v) => self.with("application/json", v),
            Err(e) => {
                self.inner = self.inner.status(StatusCode::INTERNAL_SERVER_ERROR);
                self.text(e.to_string())
            }
        }
    }

    /// The response with `application/x-www-form-urlencoded; charset=utf-8` media type.
    #[inline]
    pub fn form<T: Serialize>(mut self, value: &T) -> Self {
        match serde_urlencoded::to_string(value) {
            Ok(v) => self.with("application/x-www-form-urlencoded", v),
            Err(e) => {
                self.inner = self.inner.status(StatusCode::INTERNAL_SERVER_ERROR);
                self.text(e.to_string())
            }
        }
    }

    /// Responds to a stream.
    #[inline]
    pub fn stream<S, O, E>(mut self, stream: S) -> Self
    where
        S: futures::Stream<Item = Result<O, E>> + Send + Sync + 'static,
        O: Into<Bytes> + 'static,
        E: Into<Error> + 'static,
    {
        self.body = HttpBody::stream(stream);
        self
    }

    /// Attempts to send a file. If file not exists, not found error will occur.
    #[inline]
    pub async fn send_file<P>(self, path: P,req: crate::request::Request) -> Self 
    where
        P: Into<std::path::PathBuf> + Send,
    {
        let path = path.into();
        if !path.exists() {
            self.status(StatusCode::NOT_FOUND)
        } else {
            match crate::fs::NamedFile::builder(path).build().await {
                Ok(file) => file.send(req.headers()).await,
                Err(e) => self.status(StatusCode::INTERNAL_SERVER_ERROR).text(e.to_string())
            }
        }
    }

    #[cfg(feature = "download")]
    #[inline]
    pub fn write_file(
        mut self,
        path: impl AsRef<std::path::Path>,
        disposition_type: DispositionType,
    ) -> Result<Self, Error> {
        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));
            self.inner
                .headers_mut()
                .map(|h| h.insert("content-disposition", content_disposition.try_into().ok()?));
        }
        let body: Bytes = buffer.into();
        let body: HttpBody = body.into();
        self.body = body;
        Ok(self)
    }

    #[inline]
    pub fn build(self) -> Result<Response,Error> {
        let Builder {
            inner,
            body,
            ..
        } = self;

        let res = inner.body(body)?;
        Ok(res)
    }
}

impl Deref for Builder {
    type Target = hyper::http::response::Builder;

    #[inline]
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl DerefMut for Builder {
    #[inline]
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}