gimbal_http 0.1.0

Gimbal HTTP Networking package
Documentation
use std::{collections::BTreeMap, marker::PhantomData};

use async_trait::async_trait;
use bytes::Bytes;

use crate::{
    Error, GenericClient, GenericClientBuilder, GenericRequestBuilder, GenericResponse, Method,
    StatusCode,
};

pub struct Client(reqwest::Client);

impl Client {
    #[must_use]
    pub const fn new(client: reqwest::Client) -> Self {
        Self(client)
    }
}

impl GenericClient<crate::ReqwestRequestBuilder> for Client {
    fn request(&self, method: Method, url: &str) -> crate::ReqwestRequestBuilder {
        crate::RequestBuilderWrapper(
            RequestBuilder(Some(self.0.request(method.into(), url))),
            PhantomData,
        )
    }
}

pub struct ClientBuilder;

impl crate::ReqwestClientBuilder {
    #[must_use]
    pub const fn new() -> Self {
        Self(ClientBuilder, PhantomData, PhantomData)
    }
}

impl GenericClientBuilder<crate::ReqwestRequestBuilder, crate::ReqwestClient> for ClientBuilder {
    fn build(self) -> Result<crate::ReqwestClient, Error> {
        Ok(crate::ClientWrapper(
            Client(reqwest::Client::new()),
            PhantomData,
        ))
    }
}

pub struct RequestBuilder(Option<reqwest::RequestBuilder>);

#[async_trait]
impl GenericRequestBuilder<crate::ReqwestResponse> for RequestBuilder {
    fn header(&mut self, name: &str, value: &str) {
        let builder = self.0.take().unwrap();
        self.0 = Some(builder.header(name, value));
    }

    fn body(&mut self, body: Bytes) {
        let builder = self.0.take().unwrap();
        self.0 = Some(builder.body(body));
    }

    #[cfg(feature = "json")]
    fn form(&mut self, form: &serde_json::Value) {
        let builder = self.0.take().unwrap();
        self.0 = Some(builder.form(form));
    }

    async fn send(&mut self) -> Result<crate::ReqwestResponse, Error> {
        let builder = self.0.take().unwrap();
        Ok(crate::ResponseWrapper(Response {
            headers: None,
            inner: Some(builder.send().await?),
        }))
    }
}

pub struct Response {
    headers: Option<BTreeMap<String, String>>,
    inner: Option<reqwest::Response>,
}

#[async_trait]
impl GenericResponse for Response {
    #[must_use]
    fn status(&self) -> StatusCode {
        self.inner.as_ref().unwrap().status().into()
    }

    #[must_use]
    fn headers(&mut self) -> &BTreeMap<String, String> {
        if self.headers.is_none() {
            self.headers = Some(headers_to_btree(self.inner.as_ref().unwrap().headers()));
        }

        self.headers.as_ref().unwrap()
    }

    #[must_use]
    async fn text(&mut self) -> Result<String, Error> {
        let response = self.inner.take().unwrap();
        Ok(response.text().await?)
    }

    #[must_use]
    async fn bytes(&mut self) -> Result<Bytes, Error> {
        let response = self.inner.take().unwrap();
        Ok(response.bytes().await?)
    }

    #[must_use]
    #[cfg(feature = "stream")]
    fn bytes_stream(
        &mut self,
    ) -> std::pin::Pin<Box<dyn futures_core::Stream<Item = Result<Bytes, Error>> + Send>> {
        use futures_util::TryStreamExt as _;

        let response = self.inner.take().unwrap();
        Box::pin(response.bytes_stream().map_err(Into::into))
    }
}

fn headers_to_btree(value: &reqwest::header::HeaderMap) -> BTreeMap<String, String> {
    let mut headers = BTreeMap::new();

    for (key, value) in value {
        if let Ok(value) = value.to_str() {
            headers.insert(key.to_string(), value.to_string());
        }
    }

    headers
}