use super::AgentSpec;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidationIssue {
pub severity: IssueSeverity,
pub field: &'static str,
pub entry: String,
pub message: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IssueSeverity {
Warning,
Error,
}
impl AgentSpec {
#[must_use]
pub fn validate_catalog(&self) -> Vec<ValidationIssue> {
let mut out = Vec::new();
for (field, list) in [
("allowed_tools", self.allowed_tools.as_deref()),
("excluded_tools", self.excluded_tools.as_deref()),
] {
if let Some(entries) = list {
for e in entries {
if contains_unescaped_star(e) {
out.push(ValidationIssue {
severity: IssueSeverity::Warning,
field,
entry: e.clone(),
message: format!(
"{field}[{e}] contains '*' but literal fields never \
parse patterns; this entry will only match the literal \
tool id '{e}'. Move it to {field}_patterns if you \
intended a glob."
),
});
}
}
}
}
for (field, list) in [
(
"allowed_tool_patterns",
self.allowed_tool_patterns.as_deref(),
),
(
"excluded_tool_patterns",
self.excluded_tool_patterns.as_deref(),
),
] {
if let Some(entries) = list {
for e in entries {
if let Err(err) = awaken_tool_pattern::validate_tool_id_pattern(e) {
out.push(ValidationIssue {
severity: IssueSeverity::Error,
field,
entry: e.clone(),
message: format!("{field}[{e}] is not a valid pattern: {err}"),
});
}
}
}
}
out
}
}
fn contains_unescaped_star(s: &str) -> bool {
let b = s.as_bytes();
let mut i = 0;
while i < b.len() {
if b[i] == b'\\' && i + 1 < b.len() {
i += 2;
continue;
}
if b[i] == b'*' {
return true;
}
i += 1;
}
false
}