use anyhow::Result;
use super::fetch::{FetchConfig, fetch_screened};
use crate::OutputFormat;
use nab::task::{
FetchRequest, TaskAction, TaskFetcher, TaskOutcome, TaskStatus, discover_apis, execute_action,
};
struct CmdFetcher {
format: OutputFormat,
}
impl TaskFetcher for CmdFetcher {
async fn fetch(&self, req: FetchRequest) -> Result<String> {
let cfg = fetch_request_to_config(req, self.format);
Ok(fetch_screened(&cfg).await?.markdown)
}
}
fn fetch_request_to_config(req: FetchRequest, format: OutputFormat) -> FetchConfig {
let FetchRequest {
url,
method,
headers,
body,
} = req;
let mut cfg = FetchConfig::for_url(url, format);
cfg.method = method;
cfg.data = body;
cfg.custom_headers = headers
.iter()
.map(|(name, value)| format!("{name}: {value}"))
.collect();
cfg.raw_html = true;
cfg
}
pub async fn cmd_task(
goal: &str,
url: &str,
action_json: Option<&str>,
format: OutputFormat,
as_json: bool,
) -> Result<()> {
if let Some(raw) = action_json {
let action: TaskAction =
serde_json::from_str(raw).map_err(|e| anyhow::anyhow!("invalid --action JSON: {e}"))?;
let obs = execute_action(&action, &CmdFetcher { format }).await?;
if as_json {
println!("{}", serde_json::to_string_pretty(&obs)?);
} else {
if let Some(err) = &obs.error {
eprintln!("[task] rung {} did not complete: {err}", obs.rung);
}
if !obs.content.is_empty() {
println!("{}", obs.content);
}
}
return Ok(());
}
let cfg = FetchConfig::for_url(url.to_string(), format);
let fetched = fetch_screened(&cfg).await?;
let discovered_apis = discover_apis(&fetched.raw_html);
let outcome = TaskOutcome {
goal: goal.to_string(),
url: url.to_string(),
rung: 0, status: TaskStatus::Done,
content: fetched.markdown,
discovered_apis,
};
if as_json {
println!("{}", serde_json::to_string_pretty(&outcome)?);
} else {
println!("{}", outcome.content);
if !outcome.discovered_apis.is_empty() {
eprintln!(
"\n[task] {} rung-1 API candidate(s) discovered (use --json to see them)",
outcome.discovered_apis.len()
);
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fetch_request_to_config_maps_method_body_and_headers() {
let req = FetchRequest {
url: "https://api.example.test/v1/items".into(),
method: "POST".into(),
headers: vec![
("Authorization".into(), "Bearer t0ken".into()),
("Accept".into(), "application/json".into()),
],
body: Some(r#"{"q":"rust"}"#.into()),
};
let cfg = fetch_request_to_config(req, OutputFormat::Full);
assert_eq!(cfg.url, "https://api.example.test/v1/items");
assert_eq!(cfg.method, "POST");
assert_eq!(cfg.data.as_deref(), Some(r#"{"q":"rust"}"#));
assert!(
cfg.custom_headers
.contains(&"Authorization: Bearer t0ken".to_string())
);
assert!(
cfg.custom_headers
.contains(&"Accept: application/json".to_string())
);
assert!(cfg.raw_html);
}
}