grapheme-stdlib 0.6.0

Standard library operation implementations for Grapheme
Documentation
use serde_json::{json, Value as JsonValue};
use std::time::Duration;

pub fn request(method: &str, url: &str, body: Option<&JsonValue>) -> JsonValue {
    let request = HttpRequest::from_parts(method, url, body.cloned());
    execute(&request).to_json()
}

#[derive(Debug, Clone)]
struct HttpRequest {
    method: String,
    url: String,
    body: Option<JsonValue>,
}

impl HttpRequest {
    fn from_parts(method: &str, url: &str, body: Option<JsonValue>) -> Self {
        Self {
            method: method.to_string(),
            url: url.to_string(),
            body,
        }
    }
}

#[derive(Debug, Clone)]
struct HttpResponse {
    method: String,
    url: String,
    status: Option<u16>,
    status_line: Option<String>,
    body: Option<String>,
    error: Option<String>,
}

impl HttpResponse {
    fn error(method: &str, url: &str, error: String) -> Self {
        Self {
            method: method.to_string(),
            url: url.to_string(),
            status: None,
            status_line: None,
            body: None,
            error: Some(error),
        }
    }

    fn to_json(&self) -> JsonValue {
        if let Some(error) = &self.error {
            return json!({
                "error": error,
                "method": self.method,
                "url": self.url,
            });
        }

        json!({
            "method": self.method,
            "url": self.url,
            "status": self.status,
            "status_line": self.status_line,
            "body": self.body,
        })
    }
}

fn execute(request: &HttpRequest) -> HttpResponse {
    if !request.url.starts_with("http://") && !request.url.starts_with("https://") {
        return HttpResponse::error(
            &request.method,
            &request.url,
            "host http adapter supports only http:// and https:// URLs".to_string(),
        );
    }

    let payload = request
        .body
        .as_ref()
        .map(|b| serde_json::to_vec(b).unwrap_or_else(|_| b"null".to_vec()))
        .unwrap_or_default();

    let mut ehttp_request = if request.method == "POST" {
        let mut req = ehttp::Request::post(&request.url, payload);
        req.headers.insert("Content-Type", "application/json");
        req
    } else {
        ehttp::Request::get(&request.url)
    };
    ehttp_request.timeout = Some(Duration::from_secs(15));

    let response = match ehttp::fetch_blocking(&ehttp_request) {
        Ok(resp) => resp,
        Err(err) => {
            return HttpResponse::error(
                &request.method,
                &request.url,
                format!("request failed: {err}"),
            );
        }
    };

    let response_body = String::from_utf8_lossy(&response.bytes).to_string();
    let status_line = format!("HTTP {} {}", response.status, response.status_text);

    HttpResponse {
        method: request.method.clone(),
        url: request.url.clone(),
        status: Some(response.status),
        status_line: Some(status_line),
        body: Some(response_body),
        error: None,
    }
}