use car_ir::json_extract::{extract_json_object, repair_json};
use car_workflow::Workflow;
pub fn parse_workflow(text: &str) -> Result<Workflow, String> {
let trimmed = text.trim();
if let Ok(wf) = serde_json::from_str::<Workflow>(trimmed) {
return Ok(wf);
}
if let Some(json) = extract_json_object(trimmed) {
if let Ok(wf) = serde_json::from_str::<Workflow>(&json) {
return Ok(wf);
}
let repaired = repair_json(&json);
return serde_json::from_str::<Workflow>(&repaired).map_err(|e| {
format!("extracted a JSON object but it is not a valid workflow: {e}")
});
}
Err(format!(
"no JSON workflow object found in output ({} chars)",
text.len()
))
}
#[cfg(test)]
mod tests {
use super::*;
const MINIMAL: &str = r#"{
"id": "wf1",
"name": "Test",
"start": "a",
"stages": [
{"id": "a", "name": "A", "step": {"type": "approval", "prompt": "ok?", "fields": [], "output_key": "decision"}}
],
"edges": []
}"#;
#[test]
fn parses_clean_json() {
let wf = parse_workflow(MINIMAL).unwrap();
assert_eq!(wf.id, "wf1");
assert_eq!(wf.start, "a");
assert_eq!(wf.stages.len(), 1);
}
#[test]
fn parses_markdown_fenced() {
let text = format!("Here is the workflow:\n\n```json\n{MINIMAL}\n```\n\nDone.");
let wf = parse_workflow(&text).unwrap();
assert_eq!(wf.id, "wf1");
}
#[test]
fn parses_with_preamble() {
let text = format!("Sure! {MINIMAL}");
let wf = parse_workflow(&text).unwrap();
assert_eq!(wf.id, "wf1");
}
#[test]
fn garbage_fails() {
assert!(parse_workflow("not json at all").is_err());
}
#[test]
fn json_object_that_isnt_a_workflow_fails() {
let err = parse_workflow(r#"{"hello": "world"}"#).unwrap_err();
assert!(err.contains("not a valid workflow"));
}
}