eval_magic/validation/
evals.rs1use std::collections::HashSet;
5
6use serde_json::Value;
7
8use crate::core::EvalsConfig;
9use crate::validation::error::ValidationError;
10use crate::validation::schema::{SchemaName, validate_against_schema};
11
12pub fn validate_evals_config(config: &Value, source: &str) -> Result<EvalsConfig, ValidationError> {
16 let validated: EvalsConfig = validate_against_schema(SchemaName::Evals, config, source)?;
17
18 let mut seen = HashSet::new();
19 for (index, ev) in validated.evals.iter().enumerate() {
20 if !seen.insert(ev.id.as_str()) {
21 return Err(ValidationError::DuplicateId {
22 path: source.to_string(),
23 index,
24 id: ev.id.clone(),
25 });
26 }
27 }
28
29 Ok(validated)
30}
31
32#[cfg(test)]
33mod tests {
34 use super::validate_evals_config;
35 use serde_json::{Value, json};
36
37 fn base() -> Value {
39 json!({
40 "skill_name": "demo",
41 "evals": [
42 {
43 "id": "e1",
44 "prompt": "do the thing",
45 "expected_output": "the thing is done"
46 }
47 ]
48 })
49 }
50
51 #[test]
52 fn accepts_a_boolean_skill_should_trigger() {
53 let mut config = base();
54 config["evals"][0]["skill_should_trigger"] = json!(false);
55 let parsed = validate_evals_config(&config, "evals.json").unwrap();
56 assert_eq!(parsed.evals[0].skill_should_trigger, Some(false));
57 }
58
59 #[test]
60 fn accepts_evals_with_no_skill_should_trigger() {
61 let config = base();
62 let parsed = validate_evals_config(&config, "evals.json").unwrap();
63 assert_eq!(parsed.skill_name, "demo");
64 assert_eq!(parsed.evals[0].skill_should_trigger, None);
65 }
66
67 #[test]
68 fn rejects_a_non_boolean_skill_should_trigger() {
69 let mut config = base();
70 config["evals"][0]["skill_should_trigger"] = json!("false");
71 let err = validate_evals_config(&config, "evals.json")
72 .unwrap_err()
73 .to_string();
74 assert!(err.contains("skill_should_trigger"), "error was: {err}");
75 }
76
77 #[test]
78 fn rejects_a_non_kebab_case_id() {
79 let mut config = base();
80 config["evals"][0]["id"] = json!("Not Kebab");
81 assert!(validate_evals_config(&config, "evals.json").is_err());
82 }
83
84 #[test]
85 fn rejects_duplicate_eval_ids() {
86 let mut config = base();
87 let dup = config["evals"][0].clone();
88 config["evals"] = json!([dup.clone(), dup]);
89 let err = validate_evals_config(&config, "evals.json")
90 .unwrap_err()
91 .to_string();
92 assert!(err.contains("duplicate"), "error was: {err}");
93 }
94
95 #[test]
96 fn rejects_an_empty_evals_array() {
97 let mut config = base();
98 config["evals"] = json!([]);
99 assert!(validate_evals_config(&config, "evals.json").is_err());
100 }
101}