terapi 0.6.0

A terminal UI for REST API and GraphQL automation
use super::{HttpOutcome, HttpResult};

pub(super) async fn execute_http(
    client: reqwest::Client,
    method: &str,
    url: &str,
    headers: &[(String, String)],
    body: Option<String>,
) -> HttpOutcome {
    use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
    use std::str::FromStr;

    let t0 = std::time::Instant::now();

    let mut req = match method {
        "GET"    => client.get(url),
        "POST"   => client.post(url),
        "PUT"    => client.put(url),
        "PATCH"  => client.patch(url),
        "DELETE" => client.delete(url),
        _        => client.get(url),
    };

    if !headers.is_empty() {
        let mut hmap = HeaderMap::new();
        for (k, v) in headers {
            if let (Ok(name), Ok(val)) = (
                HeaderName::from_str(k),
                HeaderValue::from_str(v),
            ) {
                hmap.insert(name, val);
            }
        }
        req = req.headers(hmap);
    }

    if let Some(b) = body {
        req = req.body(b);
    }

    let resp = req.send().await.map_err(|e| {
        use std::error::Error;
        let mut msg = e.to_string();
        let mut src = e.source();
        while let Some(cause) = src {
            msg.push_str(&format!("\n  caused by: {}", cause));
            src = cause.source();
        }
        msg
    })?;
    let elapsed_ms = t0.elapsed().as_millis() as u64;
    let status = resp.status().as_u16();

    let headers: Vec<(String, String)> = resp
        .headers()
        .iter()
        .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string()))
        .collect();

    let body = resp.text().await.map_err(|e| e.to_string())?;

    Ok(HttpResult { status, body, headers, elapsed_ms })
}

pub(super) fn split_url_params(url: &str) -> (String, Vec<(String, String)>) {
    match url.find('?') {
        None => (url.to_string(), Vec::new()),
        Some(pos) => {
            let base = url[..pos].to_string();
            let params = url[pos + 1..]
                .split('&')
                .filter(|s| !s.is_empty())
                .map(|pair| {
                    let mut it = pair.splitn(2, '=');
                    let k = it.next().unwrap_or("").to_string();
                    let v = it.next().unwrap_or("").to_string();
                    (k, v)
                })
                .collect();
            (base, params)
        }
    }
}

pub(super) fn serialize_body_json(pairs: &[(String, String)]) -> String {
    use serde_json::{Map, Number, Value};
    let mut map = Map::new();
    for (k, v) in pairs {
        let val = if v == "null" {
            Value::Null
        } else if v == "true" {
            Value::Bool(true)
        } else if v == "false" {
            Value::Bool(false)
        } else if let Ok(n) = v.parse::<i64>() {
            Value::Number(Number::from(n))
        } else if let Ok(f) = v.parse::<f64>() {
            Value::Number(Number::from_f64(f).unwrap_or(Number::from(0)))
        } else {
            Value::String(v.clone())
        };
        map.insert(k.clone(), val);
    }
    serde_json::to_string_pretty(&Value::Object(map)).unwrap_or_else(|_| "{}".to_string())
}

pub(super) fn http_status_label(status: u16) -> String {
    let text = match status {
        200 => "200 OK",
        201 => "201 Created",
        204 => "204 No Content",
        301 => "301 Moved Permanently",
        302 => "302 Found",
        304 => "304 Not Modified",
        400 => "400 Bad Request",
        401 => "401 Unauthorized",
        403 => "403 Forbidden",
        404 => "404 Not Found",
        405 => "405 Method Not Allowed",
        409 => "409 Conflict",
        422 => "422 Unprocessable Entity",
        429 => "429 Too Many Requests",
        500 => "500 Internal Server Error",
        502 => "502 Bad Gateway",
        503 => "503 Service Unavailable",
        _   => return format!("{}", status),
    };
    text.to_string()
}