greentic-flow-builder 0.1.0

AI-powered Adaptive Card flow builder with visual graph editor and demo runner
Documentation
//! Post-processing: fix trailing commas, parse JSON, validate Adaptive Card shape.

use anyhow::{Context, bail};
use regex::Regex;
use serde_json::Value;
use std::sync::OnceLock;

static TRAILING_COMMA_RE: OnceLock<Regex> = OnceLock::new();

pub fn finalize(raw: &str) -> anyhow::Result<Value> {
    let fixed = fix_trailing_commas(raw);
    let card: Value = serde_json::from_str(&fixed)
        .context("rendered output is not valid JSON — check for template syntax errors")?;
    validate_adaptive_card_shape(&card)?;
    Ok(card)
}

pub fn fix_trailing_commas(input: &str) -> String {
    let re = TRAILING_COMMA_RE.get_or_init(|| Regex::new(r",\s*([\]}])").expect("valid regex"));
    re.replace_all(input, "$1").to_string()
}

pub fn validate_adaptive_card_shape(card: &Value) -> anyhow::Result<()> {
    let obj = card
        .as_object()
        .context("rendered card is not a JSON object")?;
    match obj.get("type").and_then(|v| v.as_str()) {
        Some("AdaptiveCard") => {}
        Some(other) => bail!("card type is \"{}\", expected \"AdaptiveCard\"", other),
        None => bail!("card missing \"type\" field"),
    }
    if obj.get("version").is_none() {
        bail!("card missing \"version\" field");
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_fix_trailing_comma_in_array() {
        assert_eq!(fix_trailing_commas("[1, 2, 3,]"), "[1, 2, 3]");
    }

    #[test]
    fn test_fix_trailing_comma_in_object() {
        assert_eq!(fix_trailing_commas(r#"{"a": 1,}"#), r#"{"a": 1}"#);
    }

    #[test]
    fn test_no_trailing_comma_untouched() {
        assert_eq!(fix_trailing_commas("[1, 2]"), "[1, 2]");
    }

    #[test]
    fn test_finalize_valid_card() {
        let raw = r#"{"type":"AdaptiveCard","version":"1.5","body":[]}"#;
        let result = finalize(raw).unwrap();
        assert_eq!(result["type"], "AdaptiveCard");
    }

    #[test]
    fn test_finalize_missing_type() {
        let raw = r#"{"version":"1.5"}"#;
        assert!(finalize(raw).is_err());
    }

    #[test]
    fn test_finalize_wrong_type() {
        let raw = r#"{"type":"Other","version":"1.5"}"#;
        assert!(finalize(raw).is_err());
    }
}