use rhai::{def_package, plugin::*};
#[derive(Default, Clone, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
enum Output {
#[default]
Text,
Json,
}
#[derive(Clone, serde::Deserialize)]
struct Parameters {
method: String,
url: String,
#[serde(default)]
headers: rhai::Array,
#[serde(default)]
body: rhai::Dynamic,
#[serde(default)]
output: Output,
}
#[export_module]
pub mod api {
use std::str::FromStr;
pub type Client = reqwest::blocking::Client;
#[rhai_fn(return_raw)]
pub fn client() -> Result<Client, Box<rhai::EvalAltResult>> {
reqwest::blocking::Client::builder()
.build()
.map_err(|error| error.to_string().into())
}
#[rhai_fn(global, pure, return_raw)]
pub fn request(
client: &mut Client,
parameters: rhai::Map,
) -> Result<rhai::Dynamic, Box<rhai::EvalAltResult>> {
let Parameters {
method,
url,
headers,
body,
output,
} = rhai::serde::from_dynamic::<Parameters>(¶meters.into())?;
let method = reqwest::Method::from_str(&method)
.map_err::<Box<rhai::EvalAltResult>, _>(|error| error.to_string().into())?;
client
.request(method, url)
.headers(
headers
.iter()
.map(|header| {
if let Some((name, value)) = header.to_string().split_once(':') {
let name = name.trim();
let value = value.trim();
let name = reqwest::header::HeaderName::from_str(name).map_err::<Box<
EvalAltResult,
>, _>(
|error| error.to_string().into(),
)?;
let value = reqwest::header::HeaderValue::from_str(value)
.map_err::<Box<EvalAltResult>, _>(|error| {
error.to_string().into()
})?;
Ok((name, value))
} else {
Err(format!("'{header}' is not a valid header").into())
}
})
.collect::<Result<reqwest::header::HeaderMap, Box<EvalAltResult>>>()?,
)
.body(body.to_string())
.send()
.and_then(|response| match output {
Output::Text => response.text().map(rhai::Dynamic::from),
Output::Json => response.json::<rhai::Map>().map(rhai::Dynamic::from),
})
.map_err(|error| format!("{error:?}").into())
}
}
def_package! {
pub HttpPackage(_module) {} |> |engine| {
engine.register_static_module("http", rhai::exported_module!(api).into());
}
}
#[cfg(test)]
pub mod test {
use crate::HttpPackage;
use rhai::packages::Package;
#[test]
fn simple_query() {
let mut engine = rhai::Engine::new();
HttpPackage::new().register_into_engine(&mut engine);
let body: String = engine
.eval(
r#"
let client = http::client();
client.request(#{ method: "GET", url: "http://example.com" })"#,
)
.unwrap();
assert!(body
.find("This domain is for use in documentation examples without needing permission.")
.is_some());
}
#[test]
fn simple_query_headers() {
let mut engine = rhai::Engine::new();
HttpPackage::new().register_into_engine(&mut engine);
let body: rhai::Map = engine
.eval(
r#"
let client = http::client();
client.request(#{
"method": "GET",
"url": "https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?slug=bitcoin&convert=EUR",
"headers": [
"X-CMC_PRO_API_KEY: xxx",
"Accept: application/json"
],
"output": "json",
})
"#,
)
.unwrap();
println!("{body:#?}");
}
}