1use std::path::Path;
2
3use crate::metadata::RuleDefinition;
4use crate::RuleEngineError;
5
6#[derive(Debug, Default)]
26pub struct RuleStore {
27 rules: Vec<RuleDefinition>,
28}
29
30impl RuleStore {
31 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub fn from_json_file(path: impl AsRef<Path>) -> Result<Self, RuleEngineError> {
38 let content = std::fs::read_to_string(path.as_ref()).map_err(|e| {
39 RuleEngineError::InvalidRule(format!(
40 "cannot read {}: {}",
41 path.as_ref().display(),
42 e
43 ))
44 })?;
45 Self::from_json_str(&content)
46 }
47
48 pub fn from_json_str(json: &str) -> Result<Self, RuleEngineError> {
50 let rules: Vec<RuleDefinition> = serde_json::from_str(json)
51 .map_err(|e| RuleEngineError::InvalidRule(format!("invalid rule JSON: {e}")))?;
52 Ok(Self { rules })
53 }
54
55 #[cfg(feature = "yaml")]
59 pub fn from_yaml_file(path: impl AsRef<Path>) -> Result<Self, RuleEngineError> {
60 let content = std::fs::read_to_string(path.as_ref()).map_err(|e| {
61 RuleEngineError::InvalidRule(format!(
62 "cannot read {}: {}",
63 path.as_ref().display(),
64 e
65 ))
66 })?;
67 Self::from_yaml_str(&content)
68 }
69
70 #[cfg(feature = "yaml")]
74 pub fn from_yaml_str(yaml: &str) -> Result<Self, RuleEngineError> {
75 let value: Value = serde_yaml::from_str(yaml)
76 .map_err(|e| RuleEngineError::InvalidRule(format!("invalid rule YAML: {e}")))?;
77 let json = serde_json::to_string(&value)
78 .map_err(|e| RuleEngineError::InvalidRule(e.to_string()))?;
79 Self::from_json_str(&json)
80 }
81
82 pub fn get(&self, name: &str) -> Option<&RuleDefinition> {
84 self.rules.iter().find(|r| r.name == name)
85 }
86
87 pub fn all(&self) -> &[RuleDefinition] {
89 &self.rules
90 }
91
92 pub fn len(&self) -> usize {
94 self.rules.len()
95 }
96
97 pub fn is_empty(&self) -> bool {
99 self.rules.is_empty()
100 }
101
102 pub fn insert(&mut self, rule: RuleDefinition) {
104 if let Some(existing) = self.rules.iter_mut().find(|r| r.name == rule.name) {
105 *existing = rule;
106 } else {
107 self.rules.push(rule);
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use serde_json::json;
116
117 fn sample_json() -> &'static str {
118 r#"[
119 {"name":"rule-a","logic":{">":[{"var":"x"},1]}},
120 {"name":"rule-b","version":"2.0.0","logic":{"==":[{"var":"y"},true]}}
121 ]"#
122 }
123
124 #[test]
125 fn load_from_json_str() {
126 let store = RuleStore::from_json_str(sample_json()).unwrap();
127 assert_eq!(store.len(), 2);
128 }
129
130 #[test]
131 fn get_by_name() {
132 let store = RuleStore::from_json_str(sample_json()).unwrap();
133 let rule = store.get("rule-b").unwrap();
134 assert_eq!(rule.version.as_deref(), Some("2.0.0"));
135 }
136
137 #[test]
138 fn get_missing_returns_none() {
139 let store = RuleStore::from_json_str(sample_json()).unwrap();
140 assert!(store.get("does-not-exist").is_none());
141 }
142
143 #[test]
144 fn insert_adds_and_replaces() {
145 let mut store = RuleStore::new();
146 store.insert(RuleDefinition::new("r", json!({})));
147 assert_eq!(store.len(), 1);
148 store.insert(RuleDefinition::new("r", json!({"==":[1,1]})));
149 assert_eq!(store.len(), 1); }
151
152 #[test]
153 fn invalid_json_returns_error() {
154 assert!(RuleStore::from_json_str("{bad}").is_err());
155 }
156}