pub fn run(
method: Option<&str>,
url: Option<&str>,
data: Option<&str>,
req_headers: Vec<String>,
json_mode: bool,
) -> anyhow::Result<()> {
let method = method.unwrap_or("GET").to_uppercase();
let Some(url) = url else {
print_usage();
return Ok(());
};
println!("rok api:test — {method} {url}");
println!("{}", "─".repeat(60));
let (status, body) = send_request(&method, url, data, &req_headers)?;
println!("Status: {status}");
println!();
if json_mode {
println!("{body}");
} else {
match serde_json::from_str::<serde_json::Value>(&body) {
Ok(v) => println!("{}", serde_json::to_string_pretty(&v)?),
Err(_) => println!("{body}"),
}
}
Ok(())
}
fn apply_headers(
mut req: ureq::RequestBuilder<ureq::typestate::WithoutBody>,
ua: &str,
headers: &[String],
) -> ureq::RequestBuilder<ureq::typestate::WithoutBody> {
req = req.header("User-Agent", ua).header("Accept", "application/json");
for h in headers {
if let Some((k, v)) = h.split_once(':') {
req = req.header(k.trim(), v.trim());
}
}
req
}
fn send_request(
method: &str,
url: &str,
data: Option<&str>,
headers: &[String],
) -> anyhow::Result<(u16, String)> {
let ua = format!("rok-cli/{}", env!("CARGO_PKG_VERSION"));
match method {
"GET" | "HEAD" | "DELETE" => {
let req_base = if method == "DELETE" { ureq::delete(url) } else { ureq::get(url) };
let req = apply_headers(req_base, &ua, headers);
let mut resp = req.call()?;
let status = resp.status().as_u16();
let body = resp.body_mut().read_to_string()?;
Ok((status, body))
}
_ => {
let json_val: serde_json::Value =
data.and_then(|s| serde_json::from_str(s).ok()).unwrap_or(serde_json::Value::Null);
let req_base = if method == "PUT" {
ureq::put(url)
} else if method == "PATCH" {
ureq::patch(url)
} else {
ureq::post(url)
};
let mut req = req_base
.header("User-Agent", &ua)
.header("Accept", "application/json")
.header("Content-Type", "application/json");
for h in headers {
if let Some((k, v)) = h.split_once(':') {
req = req.header(k.trim(), v.trim());
}
}
let mut resp = req.send_json(&json_val)?;
let status = resp.status().as_u16();
let body = resp.body_mut().read_to_string()?;
Ok((status, body))
}
}
}
fn print_usage() {
println!("rok api:test — interactive API client");
println!();
println!("Usage:");
println!(" rok api:test <URL> [options]");
println!();
println!("Options:");
println!(" --method <METHOD> HTTP method (default: GET)");
println!(" --data <JSON> Request body (JSON string)");
println!(" --header <K:V> Add a request header (repeatable)");
println!();
println!("Examples:");
println!(" rok api:test http://localhost:3000/users");
println!(" rok api:test http://localhost:3000/users --method POST --data '{{\"name\":\"Alice\"}}'");
println!(" rok api:test http://localhost:3000/me --header 'Authorization: Bearer TOKEN'");
}