use sentri_core::model::Invariant;
use sentri_core::Result;
use sentri_dsl_parser::parse_invariant;
use std::path::Path;
use tracing::info;
pub struct LibraryLoader;
impl LibraryLoader {
pub fn load_from_toml(path: &Path) -> Result<Vec<Invariant>> {
info!("Loading invariants from {:?}", path);
let content = std::fs::read_to_string(path).map_err(sentri_core::InvarError::IoError)?;
let table: toml::Table = toml::from_str(&content)
.map_err(|e| sentri_core::InvarError::ConfigError(e.to_string()))?;
let mut invariants = Vec::new();
if let Some(inv_array) = table.get("invariants").and_then(|v| v.as_array()) {
for (idx, inv_table) in inv_array.iter().enumerate() {
match parse_invariant_table(inv_table) {
Ok(inv) => {
info!("Loaded invariant: {}", inv.name);
invariants.push(inv);
}
Err(e) => {
tracing::warn!("Failed to parse invariant at index {}: {}", idx, e);
}
}
}
}
info!(
"Loaded {} invariants from {}",
invariants.len(),
path.display()
);
Ok(invariants)
}
pub fn load_from_dir(dir: &Path) -> Result<Vec<Invariant>> {
let mut all_invariants = Vec::new();
let entries = std::fs::read_dir(dir).map_err(sentri_core::InvarError::IoError)?;
for entry in entries {
let entry = entry.map_err(sentri_core::InvarError::IoError)?;
let path = entry.path();
if path.extension().is_some_and(|ext| ext == "toml") {
let invariants = Self::load_from_toml(&path)?;
all_invariants.extend(invariants);
}
}
Ok(all_invariants)
}
}
fn parse_invariant_table(table: &toml::Value) -> Result<Invariant> {
let table = table.as_table().ok_or_else(|| {
sentri_core::InvarError::ConfigError("Invariant must be a table".to_string())
})?;
let name = table
.get("name")
.and_then(|v| v.as_str())
.ok_or_else(|| {
sentri_core::InvarError::ConfigError("Invariant must have a 'name' field".to_string())
})?
.to_string();
let expression_str = table
.get("expression")
.and_then(|v| v.as_str())
.ok_or_else(|| {
sentri_core::InvarError::ConfigError(
"Invariant must have an 'expression' field".to_string(),
)
})?;
let severity = table
.get("severity")
.and_then(|v| v.as_str())
.unwrap_or("medium")
.to_string();
let category = table
.get("category")
.and_then(|v| v.as_str())
.unwrap_or("general")
.to_string();
let description = table
.get("description")
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let full_invariant_str = format!(r#"invariant {} {{ {} }}"#, name, expression_str);
let mut parsed_invariant = parse_invariant(&full_invariant_str)?;
parsed_invariant.severity = severity;
parsed_invariant.category = category;
if description.is_some() {
parsed_invariant.description = description;
}
info!(
"Parsed invariant '{}' with expression '{}' (severity: {}, category: {})",
name, expression_str, parsed_invariant.severity, parsed_invariant.category
);
Ok(parsed_invariant)
}