use regex::Regex;
use super::Recipe;
const VALID_LENSES: &[&str] = &["clarity", "focus", "narrow", "depth", "wide"];
pub fn validate(recipe: &Recipe) -> Vec<String> {
let mut errors = Vec::new();
if recipe.name.is_empty() {
errors.push("recipe name is empty".into());
}
if recipe.match_command.is_empty() {
errors.push("match_command is empty".into());
} else if let Err(e) = Regex::new(&recipe.match_command) {
errors.push(format!("match_command is not a valid regex: {e}"));
}
for (i, pat) in recipe.strip_lines.iter().enumerate() {
if let Err(e) = Regex::new(pat) {
errors.push(format!(
"strip_lines[{i}] \"{pat}\" is not a valid regex: {e}"
));
}
}
for (i, rule) in recipe.replace.iter().enumerate() {
if let Err(e) = Regex::new(&rule.from) {
errors.push(format!(
"replace[{i}].from \"{}\" is not a valid regex: {e}",
rule.from
));
}
}
for lens in &recipe.lens {
if !VALID_LENSES.contains(&lens.as_str()) {
errors.push(format!(
"unknown lens \"{lens}\" — valid values: {}",
VALID_LENSES.join(", ")
));
}
}
if let Some(budget) = recipe.budget_tokens {
if budget == 0 {
errors.push("budget_tokens must be > 0".into());
}
}
errors
}
#[cfg(test)]
mod tests {
use super::*;
use crate::recipe::RecipeReplace;
fn base_recipe() -> Recipe {
Recipe {
name: "test".into(),
match_command: "my-tool build".into(),
lens: vec![],
budget_tokens: None,
strip_lines: vec![],
replace: vec![],
on_empty: None,
scan_secrets: true,
}
}
#[test]
fn valid_recipe_no_errors() {
let r = base_recipe();
assert!(validate(&r).is_empty());
}
#[test]
fn empty_name_error() {
let mut r = base_recipe();
r.name = String::new();
assert!(validate(&r).iter().any(|e| e.contains("name")));
}
#[test]
fn invalid_match_command_regex() {
let mut r = base_recipe();
r.match_command = "[[invalid".into();
assert!(validate(&r).iter().any(|e| e.contains("match_command")));
}
#[test]
fn invalid_strip_line_regex() {
let mut r = base_recipe();
r.strip_lines = vec!["[[bad".into()];
assert!(validate(&r).iter().any(|e| e.contains("strip_lines")));
}
#[test]
fn invalid_replace_from_regex() {
let mut r = base_recipe();
r.replace = vec![RecipeReplace {
from: "[[bad".into(),
to: "ok".into(),
}];
assert!(validate(&r).iter().any(|e| e.contains("replace")));
}
#[test]
fn unknown_lens_name() {
let mut r = base_recipe();
r.lens = vec!["nonexistent".into()];
assert!(validate(&r).iter().any(|e| e.contains("lens")));
}
#[test]
fn zero_budget_error() {
let mut r = base_recipe();
r.budget_tokens = Some(0);
assert!(validate(&r).iter().any(|e| e.contains("budget")));
}
}