use std::path::Path;
use crate::metadata::RuleDefinition;
use crate::RuleEngineError;
#[derive(Debug, Default)]
pub struct RuleStore {
rules: Vec<RuleDefinition>,
}
impl RuleStore {
pub fn new() -> Self {
Self::default()
}
pub fn from_json_file(path: impl AsRef<Path>) -> Result<Self, RuleEngineError> {
let content = std::fs::read_to_string(path.as_ref()).map_err(|e| {
RuleEngineError::InvalidRule(format!(
"cannot read {}: {}",
path.as_ref().display(),
e
))
})?;
Self::from_json_str(&content)
}
pub fn from_json_str(json: &str) -> Result<Self, RuleEngineError> {
let rules: Vec<RuleDefinition> = serde_json::from_str(json)
.map_err(|e| RuleEngineError::InvalidRule(format!("invalid rule JSON: {e}")))?;
Ok(Self { rules })
}
#[cfg(feature = "yaml")]
pub fn from_yaml_file(path: impl AsRef<Path>) -> Result<Self, RuleEngineError> {
let content = std::fs::read_to_string(path.as_ref()).map_err(|e| {
RuleEngineError::InvalidRule(format!(
"cannot read {}: {}",
path.as_ref().display(),
e
))
})?;
Self::from_yaml_str(&content)
}
#[cfg(feature = "yaml")]
pub fn from_yaml_str(yaml: &str) -> Result<Self, RuleEngineError> {
let value: Value = serde_yaml::from_str(yaml)
.map_err(|e| RuleEngineError::InvalidRule(format!("invalid rule YAML: {e}")))?;
let json = serde_json::to_string(&value)
.map_err(|e| RuleEngineError::InvalidRule(e.to_string()))?;
Self::from_json_str(&json)
}
pub fn get(&self, name: &str) -> Option<&RuleDefinition> {
self.rules.iter().find(|r| r.name == name)
}
pub fn all(&self) -> &[RuleDefinition] {
&self.rules
}
pub fn len(&self) -> usize {
self.rules.len()
}
pub fn is_empty(&self) -> bool {
self.rules.is_empty()
}
pub fn insert(&mut self, rule: RuleDefinition) {
if let Some(existing) = self.rules.iter_mut().find(|r| r.name == rule.name) {
*existing = rule;
} else {
self.rules.push(rule);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn sample_json() -> &'static str {
r#"[
{"name":"rule-a","logic":{">":[{"var":"x"},1]}},
{"name":"rule-b","version":"2.0.0","logic":{"==":[{"var":"y"},true]}}
]"#
}
#[test]
fn load_from_json_str() {
let store = RuleStore::from_json_str(sample_json()).unwrap();
assert_eq!(store.len(), 2);
}
#[test]
fn get_by_name() {
let store = RuleStore::from_json_str(sample_json()).unwrap();
let rule = store.get("rule-b").unwrap();
assert_eq!(rule.version.as_deref(), Some("2.0.0"));
}
#[test]
fn get_missing_returns_none() {
let store = RuleStore::from_json_str(sample_json()).unwrap();
assert!(store.get("does-not-exist").is_none());
}
#[test]
fn insert_adds_and_replaces() {
let mut store = RuleStore::new();
store.insert(RuleDefinition::new("r", json!({})));
assert_eq!(store.len(), 1);
store.insert(RuleDefinition::new("r", json!({"==":[1,1]})));
assert_eq!(store.len(), 1); }
#[test]
fn invalid_json_returns_error() {
assert!(RuleStore::from_json_str("{bad}").is_err());
}
}