indieweb 0.10.0

A collection of utilities for working with the IndieWeb.
Documentation
#![cfg(test)]

use std::str::FromStr;

use async_trait::async_trait;
use http::header::CONNECTION;
use reqwest::{NoProxy, Proxy};

static USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "-test/", env!("CARGO_PKG_VERSION"),);

pub struct Client {
    pub mock_server: mockito::ServerGuard,
    pub http_client: reqwest::Client,
}

impl Default for Client {
    fn default() -> Self {
        let mut b = reqwest::Client::builder();

        if let Ok(proxy) = std::env::var("http_proxy") {
            b = b.proxy(Proxy::http(proxy).unwrap().no_proxy(NoProxy::from_env()));
        }

        let http_client = b.user_agent(USER_AGENT).build().unwrap();
        Self {
            mock_server: mockito::Server::new(),
            http_client,
        }
    }
}

impl Client {
    pub fn merge_into_url(&self, path: &str) -> url::Url {
        self.mock_server
            .url()
            .parse::<url::Url>()
            .expect("failed to make http URL")
            .join(path)
            .expect("failed to join URL")
    }

    pub(crate) async fn new() -> Self {
        Self {
            http_client: reqwest::Client::new(),
            mock_server: mockito::Server::new_async().await,
        }
    }
}

#[async_trait]
impl crate::http::Client for Client {
    #[tracing::instrument(skip(self))]
    async fn send_request(
        &self,
        request: http::Request<crate::http::Body>,
    ) -> Result<http::Response<crate::http::Body>, crate::Error> {
        let method = reqwest::Method::from_str(request.method().as_ref()).unwrap();
        let mut req = reqwest::Request::new(
            method,
            request
                .uri()
                .to_string()
                .parse()
                .map_err(crate::Error::Url)?,
        );
        let it = request
            .headers()
            .iter()
            .filter(|(h, _)| h != &CONNECTION)
            .map(move |(header_name, header_value)| {
                (
                    reqwest::header::HeaderName::from_str(header_name.as_str()).unwrap(),
                    reqwest::header::HeaderValue::from_bytes(header_value.as_bytes()).unwrap(),
                )
            });
        req.headers_mut().extend(it);
        *req.body_mut() = Some(reqwest::Body::from(request.body().as_bytes().to_vec()));

        let resp = self
            .http_client
            .execute(req)
            .await
            .map_err(crate::Error::from)?;

        let status = resp.status().as_u16();
        let mut whole_resp = http::Response::builder().status(status);
        for (name, value) in resp.headers() {
            whole_resp = whole_resp.header(name.as_str(), value.as_bytes());
        }
        let body = resp
            .bytes()
            .await
            .map_err(crate::Error::from)?
            .into_iter()
            .collect::<Vec<_>>()
            .into();

        whole_resp.body(body).map_err(crate::Error::Http)
    }
}