tide-etag 0.1.0

Tide middleware to generate http ETag header and respond to if-none-match header
Documentation
use lz_fnv::{Fnv1a, FnvHasher};
use tide::{Body, Request};

pub struct EtagMiddleware {}

impl Default for EtagMiddleware {
    /// Create a default etag middleware
    ///
    /// # Examples
    ///
    /// ```no_run
    ///
    /// let mut app = tide::new();
    /// app.with(tide_etag::EtagMiddleware::default());
    ///
    /// app.at("/").get(|_| async {
    ///     let mut resp = tide::Response::new(200);
    ///     resp.set_body(tide::Body::from_string("HELLO".to_string()));
    ///     Ok(resp)
    /// });
    /// app.listen("127.0.0.1:8080").await?;
    /// ```
    fn default() -> Self {
        Self {}
    }
}

#[tide::utils::async_trait]
impl<State: Clone + Send + Sync + 'static> tide::Middleware<State> for EtagMiddleware {
    async fn handle(&self, req: Request<State>, next: tide::Next<'_, State>) -> tide::Result {
        let if_none_match = req.header(tide::http::headers::IF_NONE_MATCH).cloned();
        let mut response = next.run(req).await;
        if let Some(existing_etag) = response.header(tide::http::headers::ETAG) {
            if let Some(req_hash) = if_none_match {
                if req_hash.last().as_str() == existing_etag.last().as_str() {
                    response.set_status(304);
                    response.take_body();
                    return Ok(response);
                }
            }
            return Ok(response);
        }
        let body = response.take_body();
        let mut hasher = Fnv1a::<u32>::new();
        let bytes = &body.into_bytes().await?;
        hasher.write(&bytes);
        let hash = hasher.finish();
        let generated_etag = base64::encode(hash.to_be_bytes());
        response.append_header(tide::http::headers::ETAG, &generated_etag);
        if let Some(req_hash) = if_none_match {
            if req_hash.last().as_str() == generated_etag {
                response.set_status(304);
                return Ok(response);
            }
        }
        response.set_body(Body::from_bytes(bytes.to_vec()));
        Ok(response)
    }
}