Skip to main content

aver_rt/
http.rs

1use crate::{AverList, AverStr, HttpHeaders, HttpResponse};
2
3pub fn get(url: &str) -> Result<HttpResponse, String> {
4    simple_request("GET", url)
5}
6
7pub fn head(url: &str) -> Result<HttpResponse, String> {
8    simple_request("HEAD", url)
9}
10
11pub fn delete(url: &str) -> Result<HttpResponse, String> {
12    simple_request("DELETE", url)
13}
14
15pub fn post(
16    url: &str,
17    body: &str,
18    content_type: &str,
19    headers: &HttpHeaders,
20) -> Result<HttpResponse, String> {
21    body_request("POST", url, body, content_type, headers)
22}
23
24pub fn put(
25    url: &str,
26    body: &str,
27    content_type: &str,
28    headers: &HttpHeaders,
29) -> Result<HttpResponse, String> {
30    body_request("PUT", url, body, content_type, headers)
31}
32
33pub fn patch(
34    url: &str,
35    body: &str,
36    content_type: &str,
37    headers: &HttpHeaders,
38) -> Result<HttpResponse, String> {
39    body_request("PATCH", url, body, content_type, headers)
40}
41
42fn simple_request(method: &str, url: &str) -> Result<HttpResponse, String> {
43    let result = ureq::request(method, url)
44        .timeout(std::time::Duration::from_secs(10))
45        .call();
46    response_value(result)
47}
48
49fn body_request(
50    method: &str,
51    url: &str,
52    body: &str,
53    content_type: &str,
54    headers: &HttpHeaders,
55) -> Result<HttpResponse, String> {
56    let mut req = ureq::request(method, url)
57        .timeout(std::time::Duration::from_secs(10))
58        .set("Content-Type", content_type);
59    for (name, values) in headers.iter() {
60        for value in values.iter() {
61            req = req.set(name.as_ref(), value.as_ref());
62        }
63    }
64    response_value(req.send_string(body))
65}
66
67fn response_value(result: Result<ureq::Response, ureq::Error>) -> Result<HttpResponse, String> {
68    match result {
69        Ok(resp) => build_response(resp),
70        Err(ureq::Error::Status(_, resp)) => build_response(resp),
71        Err(ureq::Error::Transport(e)) => Err(e.to_string()),
72    }
73}
74
75fn build_response(resp: ureq::Response) -> Result<HttpResponse, String> {
76    use std::io::Read;
77
78    const BODY_LIMIT: u64 = 10 * 1024 * 1024;
79
80    let status = resp.status() as i64;
81    let header_names = resp.headers_names();
82    // Build Map<lowercase-name, List<value>>. ureq deduplicates same-name
83    // headers, so each `name` here is unique already; we still wrap in a
84    // single-element list to match the multi-value type shape.
85    let mut headers: HttpHeaders = HttpHeaders::default();
86    for name in &header_names {
87        let value = AverStr::from(resp.header(name).unwrap_or(""));
88        let key = AverStr::from(name.to_ascii_lowercase());
89        let entry = match headers.get(&key) {
90            Some(existing) => AverList::prepend(value, existing).reverse(),
91            None => AverList::from_vec(vec![value]),
92        };
93        headers = headers.insert(key, entry);
94    }
95
96    let mut buf = Vec::new();
97    let bytes_read = resp
98        .into_reader()
99        .take(BODY_LIMIT + 1)
100        .read_to_end(&mut buf)
101        .map_err(|e| format!("Http: failed to read response body: {}", e))?;
102    if bytes_read as u64 > BODY_LIMIT {
103        return Err("Http: response body exceeds 10 MB limit".to_string());
104    }
105    let body = AverStr::from(String::from_utf8_lossy(&buf).into_owned());
106    Ok(HttpResponse {
107        status,
108        body,
109        headers,
110    })
111}