aboutsummaryrefslogtreecommitdiff
path: root/src/http.rs
blob: 4ea9b96a54c312f9277339b142f6e8e32094589d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use crate::error::{AppError, AppResult};
use http::{HeaderMap, HeaderName, HeaderValue, Method};
use reqwest::{Client, Request, Url};
use serde::{Deserialize, Serialize};
use serde_with::DisplayFromStr;
use serde_with::serde_as;

#[serde_as]
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct RequestConfig {
    #[serde_as(as = "DisplayFromStr")]
    #[serde(default = "RequestConfig::default_method")]
    method: Method,
    #[serde_as(as = "DisplayFromStr")]
    url: Url,
    #[serde(default)]
    headers: Vec<(String, String)>,
}

impl RequestConfig {
    pub fn new(method: Method, url: Url, headers: Vec<(String, String)>) -> Self {
        Self {
            method,
            url,
            headers,
        }
    }

    pub fn get(url: Url, headers: Vec<(String, String)>) -> Self {
        Self::new(Method::GET, url, headers)
    }

    pub fn build(&self, client: &Client) -> AppResult<Request> {
        let mut builder = client.request(self.method.clone(), self.url.clone());
        if !self.headers.is_empty() {
            builder = builder.headers(self.header_map()?);
        }
        builder = builder.body("OHAI");

        Ok(builder.build()?)
    }

    fn default_method() -> Method {
        Method::GET
    }

    fn header_map(&self) -> AppResult<HeaderMap> {
        let mut header_map = HeaderMap::new();
        for (name, value) in &self.headers {
            let name: HeaderName = name.try_into().map_err(|e| {
                AppError::InvalidHttpHeader(format!(
                    "[{}] is not a valid HTTP header name ({})",
                    name, e
                ))
            })?;
            let value: HeaderValue = value.parse().map_err(|e| {
                AppError::InvalidHttpHeader(format!(
                    "[{}] is not a valid HTTP header value ({})",
                    value, e
                ))
            })?;
            header_map.insert(name, value);
        }

        Ok(header_map)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::AppError;
    use crate::{assert_error, assert_invalid_http_header, test};
    use reqwest::ClientBuilder;

    test! {
        fn simple_get_request() {
            let url = Url::parse("https://example.com")?;
            let actual = build_get_request(&url, vec![])?;
            assert_eq!(actual.method(), Method::GET);
            assert_eq!(actual.url(), &url);
        }
    }

    test! {
        fn get_with_arbitrary_header() {
            let url = Url::parse("https://example.com")?;
            let actual = build_get_request(&url, vec![("X-Arbitrary".to_string(), "X-Arbitrary-Value".to_string())])?;
            assert_eq!(actual.headers().get("X-Arbitrary").unwrap().to_str()?, "X-Arbitrary-Value");
        }
    }

    test! {
        fn post_request_with_text_body() {
            let url = Url::parse("https://example.com")?;
            let config = RequestConfig::new(Method::POST, url, vec![]);
            let client = ClientBuilder::new().build()?;
            let request = config.build(&client)?;

            assert_eq!(request.method(), Method::POST);
        }
    }

    test! {
        fn invalid_header_name() {
            let url = Url::parse("https://example.com")?;
            let invalid_headers = vec![("Content\x00Type".to_string(), "application/json".to_string())];
            assert_invalid_http_header!(build_get_request(&url, invalid_headers), "[Content\x00Type] is not a valid HTTP header name");
        }
    }

    test! {
        fn invalid_header_value() {
            let url = Url::parse("https://example.com")?;
            let invalid_headers = vec![("Content-Type".to_string(), "application/json\x00".to_string())];
            assert_invalid_http_header!(build_get_request(&url, invalid_headers), "[application/json\x00] is not a valid HTTP header value");
        }
    }

    fn build_get_request(url: &Url, headers: Vec<(String, String)>) -> AppResult<Request> {
        let config = RequestConfig::get(url.clone(), headers);
        let client = ClientBuilder::new().build()?;
        config.build(&client)
    }
}