guardflow 0.2.0

Validators and a retry-until-valid guard for LLM output, in Rust
Documentation
use guardflow::spec::{Format, guard_from_file, guard_from_str};

const YAML_RULES: &str = r#"
rules:
  - rule: not_empty
  - rule: max_length
    max: 20
  - rule: one_of
    choices: ["yes", "no"]
"#;

const JSON_RULES: &str =
    r#"{"rules": [{"rule": "not_empty"}, {"rule": "max_length", "max": 20}, {"rule": "no_pii"}]}"#;

#[test]
fn guard_from_yaml_string_builds_a_working_guard() {
    let guard = guard_from_str(YAML_RULES, Format::Yaml).unwrap();
    assert!(guard.check("yes").passed);
    assert!(!guard.check("maybe").passed);
    assert!(!guard.check("").passed);
}

#[test]
fn guard_from_json_string_builds_a_working_guard() {
    let guard = guard_from_str(JSON_RULES, Format::Json).unwrap();
    assert!(guard.check("a short reply").passed);
    assert!(!guard.check("").passed);
    assert!(!guard.check("email me at a@b.com").passed);
}

#[test]
fn guard_from_file_picks_format_from_extension() {
    let dir = std::env::temp_dir().join(format!("guardflow-spec-test-{}", std::process::id()));
    std::fs::create_dir_all(&dir).unwrap();
    let path = dir.join("rules.yaml");
    std::fs::write(&path, YAML_RULES).unwrap();

    let guard = guard_from_file(&path).unwrap();
    assert!(guard.check("yes").passed);
    assert!(!guard.check("nope").passed);

    std::fs::remove_dir_all(&dir).unwrap();
}

#[test]
fn invalid_regex_in_a_rule_file_is_a_clear_error() {
    let bad = r#"rules: [{rule: matches_regex, pattern: "(unclosed"}]"#;
    let err = guard_from_str(bad, Format::Yaml).unwrap_err();
    assert!(matches!(err, guardflow::spec::SpecError::Regex(_)));
}