httpclient 0.26.1

HTTP client with middleware. Middleware provides composable support for record/replay, logging, exponential backoff, and more.
Documentation
use http::{HeaderName, HeaderValue, Uri};

pub use builder::RequestBuilder;
pub use memory::*;

use crate::Body;

mod builder;
mod memory;

pub type Request<T = Body> = http::Request<T>;

pub trait RequestExt {
    fn host(&self) -> &str;
    fn path(&self) -> &str;
    fn url(&self) -> &Uri;
    fn header<H: TryInto<HeaderName>>(&self, h: H) -> Option<&HeaderValue>;
    fn header_str<H: TryInto<HeaderName>>(&self, h: H) -> Option<&str>;
}

impl<B> RequestExt for Request<B> {
    fn host(&self) -> &str {
        self.uri().host().unwrap_or_default()
    }

    fn path(&self) -> &str {
        self.uri().path()
    }

    fn url(&self) -> &Uri {
        self.uri()
    }

    fn header<H: TryInto<HeaderName>>(&self, h: H) -> Option<&HeaderValue> {
        let h = h.try_into().ok()?;
        self.headers().get(h)
    }

    fn header_str<H: TryInto<HeaderName>>(&self, h: H) -> Option<&str> {
        self.header(h).and_then(|v| v.to_str().ok())
    }
}

pub trait RequestBuilderExt {
    fn get(uri: &str) -> RequestBuilder<()>;
    fn post(uri: &str) -> RequestBuilder<()>;
    fn put(uri: &str) -> RequestBuilder<()>;
    fn delete(uri: &str) -> RequestBuilder<()>;
    fn head(uri: &str) -> RequestBuilder<()>;
}

impl RequestBuilderExt for Request {
    fn get(uri: &str) -> RequestBuilder<()> {
        RequestBuilder::get(uri)
    }

    fn post(uri: &str) -> RequestBuilder<()> {
        RequestBuilder::post(uri)
    }

    fn put(uri: &str) -> RequestBuilder<()> {
        RequestBuilder::put(uri)
    }

    fn delete(uri: &str) -> RequestBuilder<()> {
        RequestBuilder::delete(uri)
    }

    fn head(uri: &str) -> RequestBuilder<()> {
        RequestBuilder::head(uri)
    }
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use serde_json::json;

    use crate::{Client, InMemoryBody};

    use super::*;

    #[test]
    fn test_push_query() {
        let mut r1 = RequestBuilder::get("https://example.com/foo/bar");
        r1 = r1.query("a", "b");

        assert_eq!(r1.uri.to_string(), "https://example.com/foo/bar?a=b");
        r1 = r1.query("c", "d");
        assert_eq!(r1.uri.to_string(), "https://example.com/foo/bar?a=b&c=d");
    }

    #[test]
    fn test_query() {
        let r1 = RequestBuilder::get("http://example.com/foo/bar").set_query(HashMap::from([("a", Some("b")), ("c", Some("d")), ("e", None)]));
        let r1 = r1.body(InMemoryBody::Empty);
        let value: HashMap<String, String> = serde_qs::from_str(r1.uri.query().unwrap()).unwrap();
        assert_eq!(value.get("a"), Some(&"b".to_string()));
        assert_eq!(value.get("c"), Some(&"d".to_string()));
        assert_eq!(value.len(), 2);
        assert!(r1.uri.to_string().starts_with("http://example.com/foo/bar?"));
    }

    #[test]
    fn test_client_request() {
        let client = Client::new();
        let _ = client.post("/foo").json(json!({"a": 1}));
    }
}