aver-rt 0.4.4

Shared Rust runtime pieces for Aver-generated programs
Documentation
use crate::{AverList, AverStr, HttpHeaders, HttpResponse};

pub fn get(url: &str) -> Result<HttpResponse, String> {
    simple_request("GET", url)
}

pub fn head(url: &str) -> Result<HttpResponse, String> {
    simple_request("HEAD", url)
}

pub fn delete(url: &str) -> Result<HttpResponse, String> {
    simple_request("DELETE", url)
}

pub fn post(
    url: &str,
    body: &str,
    content_type: &str,
    headers: &HttpHeaders,
) -> Result<HttpResponse, String> {
    body_request("POST", url, body, content_type, headers)
}

pub fn put(
    url: &str,
    body: &str,
    content_type: &str,
    headers: &HttpHeaders,
) -> Result<HttpResponse, String> {
    body_request("PUT", url, body, content_type, headers)
}

pub fn patch(
    url: &str,
    body: &str,
    content_type: &str,
    headers: &HttpHeaders,
) -> Result<HttpResponse, String> {
    body_request("PATCH", url, body, content_type, headers)
}

fn simple_request(method: &str, url: &str) -> Result<HttpResponse, String> {
    let result = ureq::request(method, url)
        .timeout(std::time::Duration::from_secs(10))
        .call();
    response_value(result)
}

fn body_request(
    method: &str,
    url: &str,
    body: &str,
    content_type: &str,
    headers: &HttpHeaders,
) -> Result<HttpResponse, String> {
    let mut req = ureq::request(method, url)
        .timeout(std::time::Duration::from_secs(10))
        .set("Content-Type", content_type);
    for (name, values) in headers.iter() {
        for value in values.iter() {
            req = req.set(name.as_ref(), value.as_ref());
        }
    }
    response_value(req.send_string(body))
}

fn response_value(result: Result<ureq::Response, ureq::Error>) -> Result<HttpResponse, String> {
    match result {
        Ok(resp) => build_response(resp),
        Err(ureq::Error::Status(_, resp)) => build_response(resp),
        Err(ureq::Error::Transport(e)) => Err(e.to_string()),
    }
}

fn build_response(resp: ureq::Response) -> Result<HttpResponse, String> {
    use std::io::Read;

    const BODY_LIMIT: u64 = 10 * 1024 * 1024;

    let status = resp.status() as i64;
    let header_names = resp.headers_names();
    // Build Map<lowercase-name, List<value>>. ureq deduplicates same-name
    // headers, so each `name` here is unique already; we still wrap in a
    // single-element list to match the multi-value type shape.
    let mut headers: HttpHeaders = HttpHeaders::default();
    for name in &header_names {
        let value = AverStr::from(resp.header(name).unwrap_or(""));
        let key = AverStr::from(name.to_ascii_lowercase());
        let entry = match headers.get(&key) {
            Some(existing) => AverList::prepend(value, existing).reverse(),
            None => AverList::from_vec(vec![value]),
        };
        headers = headers.insert(key, entry);
    }

    let mut buf = Vec::new();
    let bytes_read = resp
        .into_reader()
        .take(BODY_LIMIT + 1)
        .read_to_end(&mut buf)
        .map_err(|e| format!("Http: failed to read response body: {}", e))?;
    if bytes_read as u64 > BODY_LIMIT {
        return Err("Http: response body exceeds 10 MB limit".to_string());
    }
    let body = AverStr::from(String::from_utf8_lossy(&buf).into_owned());
    Ok(HttpResponse {
        status,
        body,
        headers,
    })
}