use crate::marshal::register_typed_async_fn_3_full;
use crate::marshal::register_typed_async_fn_2_full;
use crate::module_exports::{ModuleExports, ModuleParam};
use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
use shape_value::heap_value::HeapValue;
use std::sync::Arc;
fn build_response_pairs(
status: u16,
headers: Vec<(String, String)>,
body: String,
) -> Vec<(String, ConcreteReturn)> {
vec![
("status".to_string(), ConcreteReturn::I64(status as i64)),
(
"headers".to_string(),
ConcreteReturn::HashMapStringString(headers),
),
("body".to_string(), ConcreteReturn::String(body)),
(
"ok".to_string(),
ConcreteReturn::Bool((200..300).contains(&status)),
),
]
}
fn extract_headers(options: &[(Arc<String>, Arc<HeapValue>)]) -> Vec<(String, String)> {
for (k, v) in options.iter() {
if k.as_str() == "headers" {
if let HeapValue::HashMap(kref) = &**v {
use shape_value::heap_value::HashMapKindedRef;
if let HashMapKindedRef::String(arc) = kref {
let n = arc.len();
let mut out = Vec::with_capacity(n);
for i in 0..n {
let key: String = unsafe {
let ptr = shape_value::v2::typed_array::TypedArray::get_unchecked(
arc.keys, i as u32,
);
shape_value::v2::string_obj::StringObj::as_str(ptr).to_owned()
};
let val: String = unsafe {
let v_ptr: *const shape_value::v2::string_obj::StringObj =
*(*arc.values).data.add(i);
shape_value::v2::string_obj::StringObj::as_str(v_ptr).to_owned()
};
out.push((key, val));
}
return out;
}
return Vec::new();
}
}
}
Vec::new()
}
fn extract_timeout(
options: &[(Arc<String>, Arc<HeapValue>)],
) -> Option<std::time::Duration> {
for (k, v) in options.iter() {
if k.as_str() == "timeout" {
if let HeapValue::BigInt(ms) = &**v {
let n = **ms;
if n > 0 {
return Some(std::time::Duration::from_millis(n as u64));
}
}
}
}
None
}
pub fn create_http_module() -> ModuleExports {
let mut module = ModuleExports::new("std::core::http");
module.description = "HTTP client for making web requests".to_string();
let url_param = ModuleParam {
name: "url".to_string(),
type_name: "string".to_string(),
required: true,
description: "URL to request".to_string(),
..Default::default()
};
let options_param = ModuleParam {
name: "options".to_string(),
type_name: "HashMap<string, any>".to_string(),
required: false,
description: "Request options: { headers?: HashMap, timeout?: int }"
.to_string(),
default_snippet: Some("{}".to_string()),
..Default::default()
};
let response_ty =
ConcreteType::Result(Box::new(ConcreteType::Named("HttpResponse".to_string())));
register_typed_async_fn_2_full::<_, _, Arc<String>, Vec<(Arc<String>, Arc<HeapValue>)>>(
&mut module,
"get",
"Perform an HTTP GET request",
[url_param.clone(), options_param.clone()],
response_ty.clone(),
|url: Arc<String>, options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
let mut builder = reqwest::Client::new().get(url.as_str());
for (k, v) in extract_headers(&options) {
builder = builder.header(&k, &v);
}
if let Some(timeout) = extract_timeout(&options) {
builder = builder.timeout(timeout);
}
let resp = builder
.send()
.await
.map_err(|e| format!("http.get() failed: {}", e))?;
let status = resp.status().as_u16();
let headers: Vec<(String, String)> = resp
.headers()
.iter()
.map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body = resp
.text()
.await
.map_err(|e| format!("http.get() body read failed: {}", e))?;
Ok(TypedReturn::OkObjectPairs(build_response_pairs(
status, headers, body,
)))
},
);
register_typed_async_fn_2_full::<_, _, Arc<String>, Vec<(Arc<String>, Arc<HeapValue>)>>(
&mut module,
"delete",
"Perform an HTTP DELETE request",
[url_param, options_param],
response_ty,
|url: Arc<String>, options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
let mut builder = reqwest::Client::new().delete(url.as_str());
for (k, v) in extract_headers(&options) {
builder = builder.header(&k, &v);
}
if let Some(timeout) = extract_timeout(&options) {
builder = builder.timeout(timeout);
}
let resp = builder
.send()
.await
.map_err(|e| format!("http.delete() failed: {}", e))?;
let status = resp.status().as_u16();
let headers: Vec<(String, String)> = resp
.headers()
.iter()
.map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body = resp
.text()
.await
.map_err(|e| format!("http.delete() body read failed: {}", e))?;
Ok(TypedReturn::OkObjectPairs(build_response_pairs(
status, headers, body,
)))
},
);
let url_param_3 = ModuleParam {
name: "url".to_string(),
type_name: "string".to_string(),
required: true,
description: "URL to request".to_string(),
..Default::default()
};
let options_param_3 = ModuleParam {
name: "options".to_string(),
type_name: "HashMap<string, any>".to_string(),
required: false,
description: "Request options: { headers?: HashMap, timeout?: int }"
.to_string(),
default_snippet: Some("{}".to_string()),
..Default::default()
};
let body_text_param = ModuleParam {
name: "body".to_string(),
type_name: "string".to_string(),
required: true,
description: "Request body as a string (sent verbatim)".to_string(),
..Default::default()
};
let body_bytes_param = ModuleParam {
name: "body".to_string(),
type_name: "Array<int>".to_string(),
required: true,
description: "Request body as a byte array".to_string(),
..Default::default()
};
let response_ty_3 =
ConcreteType::Result(Box::new(ConcreteType::Named("HttpResponse".to_string())));
register_typed_async_fn_3_full::<
_,
_,
Arc<String>,
Arc<String>,
Vec<(Arc<String>, Arc<HeapValue>)>,
>(
&mut module,
"post_text",
"Perform an HTTP POST request with a text body",
[
url_param_3.clone(),
body_text_param.clone(),
options_param_3.clone(),
],
response_ty_3.clone(),
|url: Arc<String>,
body: Arc<String>,
options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
let mut builder = reqwest::Client::new()
.post(url.as_str())
.header(
reqwest::header::CONTENT_TYPE,
"text/plain; charset=utf-8",
)
.body(body.as_str().to_string());
for (k, v) in extract_headers(&options) {
builder = builder.header(&k, &v);
}
if let Some(timeout) = extract_timeout(&options) {
builder = builder.timeout(timeout);
}
let resp = builder
.send()
.await
.map_err(|e| format!("http.post_text() failed: {}", e))?;
let status = resp.status().as_u16();
let headers: Vec<(String, String)> = resp
.headers()
.iter()
.map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body_out = resp
.text()
.await
.map_err(|e| format!("http.post_text() body read failed: {}", e))?;
Ok(TypedReturn::OkObjectPairs(build_response_pairs(
status, headers, body_out,
)))
},
);
register_typed_async_fn_3_full::<
_,
_,
Arc<String>,
Vec<u8>,
Vec<(Arc<String>, Arc<HeapValue>)>,
>(
&mut module,
"post_bytes",
"Perform an HTTP POST request with a binary body",
[
url_param_3.clone(),
body_bytes_param.clone(),
options_param_3.clone(),
],
response_ty_3.clone(),
|url: Arc<String>,
body: Vec<u8>,
options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
let mut builder = reqwest::Client::new()
.post(url.as_str())
.header(
reqwest::header::CONTENT_TYPE,
"application/octet-stream",
)
.body(body);
for (k, v) in extract_headers(&options) {
builder = builder.header(&k, &v);
}
if let Some(timeout) = extract_timeout(&options) {
builder = builder.timeout(timeout);
}
let resp = builder
.send()
.await
.map_err(|e| format!("http.post_bytes() failed: {}", e))?;
let status = resp.status().as_u16();
let headers: Vec<(String, String)> = resp
.headers()
.iter()
.map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body_out = resp
.text()
.await
.map_err(|e| format!("http.post_bytes() body read failed: {}", e))?;
Ok(TypedReturn::OkObjectPairs(build_response_pairs(
status, headers, body_out,
)))
},
);
register_typed_async_fn_3_full::<
_,
_,
Arc<String>,
Arc<String>,
Vec<(Arc<String>, Arc<HeapValue>)>,
>(
&mut module,
"put_text",
"Perform an HTTP PUT request with a text body",
[
url_param_3.clone(),
body_text_param,
options_param_3.clone(),
],
response_ty_3.clone(),
|url: Arc<String>,
body: Arc<String>,
options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
let mut builder = reqwest::Client::new()
.put(url.as_str())
.header(
reqwest::header::CONTENT_TYPE,
"text/plain; charset=utf-8",
)
.body(body.as_str().to_string());
for (k, v) in extract_headers(&options) {
builder = builder.header(&k, &v);
}
if let Some(timeout) = extract_timeout(&options) {
builder = builder.timeout(timeout);
}
let resp = builder
.send()
.await
.map_err(|e| format!("http.put_text() failed: {}", e))?;
let status = resp.status().as_u16();
let headers: Vec<(String, String)> = resp
.headers()
.iter()
.map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body_out = resp
.text()
.await
.map_err(|e| format!("http.put_text() body read failed: {}", e))?;
Ok(TypedReturn::OkObjectPairs(build_response_pairs(
status, headers, body_out,
)))
},
);
register_typed_async_fn_3_full::<
_,
_,
Arc<String>,
Vec<u8>,
Vec<(Arc<String>, Arc<HeapValue>)>,
>(
&mut module,
"put_bytes",
"Perform an HTTP PUT request with a binary body",
[url_param_3, body_bytes_param, options_param_3],
response_ty_3,
|url: Arc<String>,
body: Vec<u8>,
options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
let mut builder = reqwest::Client::new()
.put(url.as_str())
.header(
reqwest::header::CONTENT_TYPE,
"application/octet-stream",
)
.body(body);
for (k, v) in extract_headers(&options) {
builder = builder.header(&k, &v);
}
if let Some(timeout) = extract_timeout(&options) {
builder = builder.timeout(timeout);
}
let resp = builder
.send()
.await
.map_err(|e| format!("http.put_bytes() failed: {}", e))?;
let status = resp.status().as_u16();
let headers: Vec<(String, String)> = resp
.headers()
.iter()
.map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body_out = resp
.text()
.await
.map_err(|e| format!("http.put_bytes() body read failed: {}", e))?;
Ok(TypedReturn::OkObjectPairs(build_response_pairs(
status, headers, body_out,
)))
},
);
let url_param_post_json = ModuleParam {
name: "url".to_string(),
type_name: "string".to_string(),
required: true,
description: "URL to request".to_string(),
..Default::default()
};
let body_object_param_post = ModuleParam {
name: "body".to_string(),
type_name: "object".to_string(),
required: true,
description: "Request body as an object (sent as JSON)".to_string(),
..Default::default()
};
let options_param_post_json = ModuleParam {
name: "options".to_string(),
type_name: "HashMap<string, any>".to_string(),
required: false,
description: "Request options: { headers?: HashMap, timeout?: int }"
.to_string(),
default_snippet: Some("{}".to_string()),
..Default::default()
};
let response_ty_post_json =
ConcreteType::Result(Box::new(ConcreteType::Named("HttpResponse".to_string())));
register_typed_async_fn_3_full::<
_,
_,
Arc<String>,
Vec<(Arc<String>, Arc<HeapValue>)>,
Vec<(Arc<String>, Arc<HeapValue>)>,
>(
&mut module,
"post_json",
"Perform an HTTP POST request with a JSON body",
[
url_param_post_json.clone(),
body_object_param_post,
options_param_post_json.clone(),
],
response_ty_post_json.clone(),
|url: Arc<String>,
body: Vec<(Arc<String>, Arc<HeapValue>)>,
options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
let mut json_pairs: Vec<(String, crate::json_value::JsonValue)> =
Vec::with_capacity(body.len());
for (k, v) in body.iter() {
json_pairs.push(((**k).clone(), crate::json_value::heap_to_json_value(v)?));
}
let json_value = crate::json_value::JsonValue::Object(json_pairs);
let serde_json_v = crate::json_value::json_value_to_serde_json(&json_value);
let body_str = serde_json::to_string(&serde_json_v)
.map_err(|e| format!("http.post_json() body serialization failed: {}", e))?;
let mut builder = reqwest::Client::new()
.post(url.as_str())
.header(reqwest::header::CONTENT_TYPE, "application/json")
.body(body_str);
for (k, v) in extract_headers(&options) {
builder = builder.header(&k, &v);
}
if let Some(timeout) = extract_timeout(&options) {
builder = builder.timeout(timeout);
}
let resp = builder
.send()
.await
.map_err(|e| format!("http.post_json() failed: {}", e))?;
let status = resp.status().as_u16();
let headers: Vec<(String, String)> = resp
.headers()
.iter()
.map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body_out = resp
.text()
.await
.map_err(|e| format!("http.post_json() body read failed: {}", e))?;
Ok(TypedReturn::OkObjectPairs(build_response_pairs(
status, headers, body_out,
)))
},
);
let body_object_param_put = ModuleParam {
name: "body".to_string(),
type_name: "object".to_string(),
required: true,
description: "Request body as an object (sent as JSON)".to_string(),
..Default::default()
};
register_typed_async_fn_3_full::<
_,
_,
Arc<String>,
Vec<(Arc<String>, Arc<HeapValue>)>,
Vec<(Arc<String>, Arc<HeapValue>)>,
>(
&mut module,
"put_json",
"Perform an HTTP PUT request with a JSON body",
[url_param_post_json, body_object_param_put, options_param_post_json],
response_ty_post_json,
|url: Arc<String>,
body: Vec<(Arc<String>, Arc<HeapValue>)>,
options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
let mut json_pairs: Vec<(String, crate::json_value::JsonValue)> =
Vec::with_capacity(body.len());
for (k, v) in body.iter() {
json_pairs.push(((**k).clone(), crate::json_value::heap_to_json_value(v)?));
}
let json_value = crate::json_value::JsonValue::Object(json_pairs);
let serde_json_v = crate::json_value::json_value_to_serde_json(&json_value);
let body_str = serde_json::to_string(&serde_json_v)
.map_err(|e| format!("http.put_json() body serialization failed: {}", e))?;
let mut builder = reqwest::Client::new()
.put(url.as_str())
.header(reqwest::header::CONTENT_TYPE, "application/json")
.body(body_str);
for (k, v) in extract_headers(&options) {
builder = builder.header(&k, &v);
}
if let Some(timeout) = extract_timeout(&options) {
builder = builder.timeout(timeout);
}
let resp = builder
.send()
.await
.map_err(|e| format!("http.put_json() failed: {}", e))?;
let status = resp.status().as_u16();
let headers: Vec<(String, String)> = resp
.headers()
.iter()
.map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
.collect();
let body_out = resp
.text()
.await
.map_err(|e| format!("http.put_json() body read failed: {}", e))?;
Ok(TypedReturn::OkObjectPairs(build_response_pairs(
status, headers, body_out,
)))
},
);
module
}