Skip to main content

grapheme_stdlib/
http.rs

1use serde_json::{json, Value as JsonValue};
2use std::time::Duration;
3
4pub fn request(method: &str, url: &str, body: Option<&JsonValue>) -> JsonValue {
5    let request = HttpRequest::from_parts(method, url, body.cloned());
6    execute(&request).to_json()
7}
8
9#[derive(Debug, Clone)]
10struct HttpRequest {
11    method: String,
12    url: String,
13    body: Option<JsonValue>,
14}
15
16impl HttpRequest {
17    fn from_parts(method: &str, url: &str, body: Option<JsonValue>) -> Self {
18        Self {
19            method: method.to_string(),
20            url: url.to_string(),
21            body,
22        }
23    }
24}
25
26#[derive(Debug, Clone)]
27struct HttpResponse {
28    method: String,
29    url: String,
30    status: Option<u16>,
31    status_line: Option<String>,
32    body: Option<String>,
33    error: Option<String>,
34}
35
36impl HttpResponse {
37    fn error(method: &str, url: &str, error: String) -> Self {
38        Self {
39            method: method.to_string(),
40            url: url.to_string(),
41            status: None,
42            status_line: None,
43            body: None,
44            error: Some(error),
45        }
46    }
47
48    fn to_json(&self) -> JsonValue {
49        if let Some(error) = &self.error {
50            return json!({
51                "error": error,
52                "method": self.method,
53                "url": self.url,
54            });
55        }
56
57        json!({
58            "method": self.method,
59            "url": self.url,
60            "status": self.status,
61            "status_line": self.status_line,
62            "body": self.body,
63        })
64    }
65}
66
67fn execute(request: &HttpRequest) -> HttpResponse {
68    if !request.url.starts_with("http://") && !request.url.starts_with("https://") {
69        return HttpResponse::error(
70            &request.method,
71            &request.url,
72            "host http adapter supports only http:// and https:// URLs".to_string(),
73        );
74    }
75
76    let payload = request
77        .body
78        .as_ref()
79        .map(|b| serde_json::to_vec(b).unwrap_or_else(|_| b"null".to_vec()))
80        .unwrap_or_default();
81
82    let mut ehttp_request = if request.method == "POST" {
83        let mut req = ehttp::Request::post(&request.url, payload);
84        req.headers.insert("Content-Type", "application/json");
85        req
86    } else {
87        ehttp::Request::get(&request.url)
88    };
89    ehttp_request.timeout = Some(Duration::from_secs(15));
90
91    let response = match ehttp::fetch_blocking(&ehttp_request) {
92        Ok(resp) => resp,
93        Err(err) => {
94            return HttpResponse::error(
95                &request.method,
96                &request.url,
97                format!("request failed: {err}"),
98            );
99        }
100    };
101
102    let response_body = String::from_utf8_lossy(&response.bytes).to_string();
103    let status_line = format!("HTTP {} {}", response.status, response.status_text);
104
105    HttpResponse {
106        method: request.method.clone(),
107        url: request.url.clone(),
108        status: Some(response.status),
109        status_line: Some(status_line),
110        body: Some(response_body),
111        error: None,
112    }
113}