chipp_http 1.2.0

curl-based HTTP client library
Documentation
use std::borrow::Borrow;
use std::fmt;

use url::Url;

use crate::hexdump::hexdump;

pub struct Request {
    pub url: Url,
    pub method: HttpMethod,
    pub headers: Option<Vec<(String, String)>>,
    pub form: Option<Vec<(String, String)>>,
    pub body: Option<Vec<u8>>,
    pub retry_count: Option<u8>,
}

impl fmt::Debug for Request {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let alternate = f.alternate();

        let mut debug = f.debug_struct("Request");
        debug
            .field("method", &self.method)
            .field("url", &self.url.to_string());

        if let Some(body) = self.body.as_ref() {
            if alternate {
                debug.finish()?;

                writeln!(f)?;
                hexdump(body, f)
            } else {
                debug.field("body", &format!("{} bytes", body.len()));
                debug.finish()
            }
        } else {
            debug.finish()
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum HttpMethod {
    Get,
    Post,
    Put,
    Delete,
}

impl Default for HttpMethod {
    fn default() -> HttpMethod {
        HttpMethod::Get
    }
}

impl Request {
    pub fn new<'r>(url: Url) -> Request {
        Request {
            url,
            method: HttpMethod::default(),
            form: None,
            headers: None,
            body: None,
            retry_count: None,
        }
    }
}

impl Request {
    pub fn set_method(&mut self, method: HttpMethod) {
        self.method = method
    }

    pub fn add_header<H, V>(&mut self, header: H, value: V)
    where
        H: ToString,
        V: ToString,
    {
        if let None = self.headers {
            self.headers = Some(vec![]);
        }

        self.headers
            .as_mut()
            .unwrap()
            .push((header.to_string(), value.to_string()));
    }

    pub fn set_form<I, K, V>(&mut self, form_iter: I)
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: ToString,
        V: ToString,
    {
        let mut form = vec![];

        for pair in form_iter.into_iter() {
            let &(ref k, ref v) = pair.borrow();
            form.push((k.to_string(), v.to_string()));
        }

        self.form = Some(form)
    }

    pub fn set_urlencoded_params<I, K, V>(&mut self, params: I)
    where
        I: IntoIterator,
        I::Item: Borrow<(K, V)>,
        K: AsRef<str>,
        V: AsRef<str>,
    {
        let mut serializer = url::form_urlencoded::Serializer::new(String::default());

        for pair in params.into_iter() {
            let &(ref k, ref v) = pair.borrow();
            serializer.append_pair(k.as_ref(), v.as_ref());
        }

        self.body = Some(serializer.finish().into_bytes())
    }

    pub fn set_json_body<J: serde::Serialize>(&mut self, json: &J) {
        let body = serde_json::to_vec(&json).expect("valid json argument");
        self.body = Some(body);
        self.add_header("Content-Type", "application/json")
    }

    pub fn set_retry_count(&mut self, retry_count: u8) {
        self.retry_count = Some(retry_count)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_debug() {
        let mut req = Request::new(Url::parse("https://example.com").unwrap());
        req.set_method(HttpMethod::Post);

        assert_eq!(
            format!("{:?}", req),
            r#"Request { method: Post, url: "https://example.com/" }"#
        );
    }
}