use serde_json::{Value, json};
use crate::json::str_of;
#[derive(Debug, Clone, PartialEq)]
pub struct Invocation {
pub id: String,
pub tool: String,
pub input: Value,
pub cwd: Option<String>,
}
impl Eq for Invocation {}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Capture {
pub stdout: String,
pub stderr: String,
pub exit_code: i32,
}
pub fn invocation_value(invocation: &Invocation) -> Value {
let mut o = json!({ "invocation": invocation.id, "tool": invocation.tool,
"input": invocation.input });
if let (Some(cwd), Some(map)) = (&invocation.cwd, o.as_object_mut()) {
map.insert("cwd".to_owned(), Value::String(cwd.clone()));
}
o
}
pub fn invocation_of(v: &Value) -> Result<Invocation, String> {
let o = v.as_object().ok_or("invocation: not a JSON object")?;
Ok(Invocation {
id: str_of(o, "invocation").map_err(|e| format!("invocation: {e}"))?,
tool: str_of(o, "tool").map_err(|e| format!("invocation: {e}"))?,
input: o
.get("input")
.cloned()
.ok_or("invocation: missing field \"input\"")?,
cwd: match o.get("cwd") {
None | Some(Value::Null) => None,
Some(Value::String(s)) => Some(s.clone()),
Some(_) => return Err("invocation: field \"cwd\" is not a string".to_owned()),
},
})
}
pub fn capture_value(capture: &Capture) -> Value {
json!({ "stdout": capture.stdout, "stderr": capture.stderr,
"exit_code": capture.exit_code })
}
pub fn capture_of(v: &Value) -> Result<Capture, String> {
let o = v.as_object().ok_or("capture: not a JSON object")?;
Ok(Capture {
stdout: str_of(o, "stdout").map_err(|e| format!("capture: {e}"))?,
stderr: str_of(o, "stderr").map_err(|e| format!("capture: {e}"))?,
exit_code: exit_of(o.get("exit_code"))?,
})
}
fn exit_of(field: Option<&Value>) -> Result<i32, String> {
let code = field
.and_then(Value::as_i64)
.ok_or("capture: missing or non-integer field \"exit_code\"")?;
i32::try_from(code).map_err(|_| format!("capture: exit_code {code} out of range"))
}
#[cfg(test)]
mod tests;