aboutsummaryrefslogtreecommitdiff
path: root/src/http.rs
blob: db5e3a6dbd543ff41d91d09a529e930a1498e759 (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
use serde_with::DisplayFromStr;
use http::{HeaderMap, HeaderName, HeaderValue, Method};
use reqwest::{Client, Request, Url};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use crate::error::{AppError, AppResult};

#[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()?);
        }
        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 crate::AppError;
    use crate::{assert_error, assert_invalid_http_header, test};
    use reqwest::ClientBuilder;
    use super::*;

    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 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)
    }

}