use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum ScriptOutput {
Observation {
value: Value,
},
Error {
message: String,
},
FinalResult {
value: Value,
},
TransferToAgent {
agent_name: String,
},
}
impl ScriptOutput {
pub fn decode(value: Value) -> Self {
match serde_json::from_value::<ScriptOutput>(value.clone()) {
Ok(output) => output,
Err(err) => ScriptOutput::Error {
message: format!(
"script must return an observation/error/final_result value; got {value} ({err})"
),
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn script_output_round_trips() {
let cases = [
ScriptOutput::Observation { value: json!({"rows": 3}) },
ScriptOutput::FinalResult { value: json!("done") },
ScriptOutput::Error { message: "boom".to_string() },
ScriptOutput::TransferToAgent { agent_name: "billing".to_string() },
];
for case in cases {
let encoded = serde_json::to_value(&case).unwrap();
let decoded: ScriptOutput = serde_json::from_value(encoded).unwrap();
assert_eq!(case, decoded);
}
}
#[test]
fn final_result_wire_shape_is_tagged() {
let encoded = serde_json::to_value(ScriptOutput::FinalResult { value: json!(7) }).unwrap();
assert_eq!(encoded, json!({"type": "final_result", "value": 7}));
}
#[test]
fn transfer_wire_shape_is_tagged() {
let encoded = serde_json::to_value(ScriptOutput::TransferToAgent {
agent_name: "billing".to_string(),
})
.unwrap();
assert_eq!(encoded, json!({"type": "transfer_to_agent", "agent_name": "billing"}));
}
#[test]
fn decode_rejects_non_variant() {
let out = ScriptOutput::decode(json!({"not": "a variant"}));
assert!(matches!(out, ScriptOutput::Error { .. }));
}
}