1use serde::Serialize;
6use serde::de::DeserializeOwned;
7
8pub trait RuleConfig: Serialize + DeserializeOwned + Default + Clone {
10 const RULE_NAME: &'static str;
12}
13
14pub fn load_rule_config<T: RuleConfig>(config: &crate::config::Config) -> T {
19 config
20 .rules
21 .get(T::RULE_NAME)
22 .and_then(|rule_config| {
23 let mut table = toml::map::Map::new();
25
26 for (k, v) in rule_config.values.iter() {
27 table.insert(k.clone(), v.clone());
29 }
30
31 let toml_table = toml::Value::Table(table);
32
33 match toml_table.try_into::<T>() {
35 Ok(config) => Some(config),
36 Err(e) => {
37 eprintln!("Warning: Invalid configuration for rule {}: {}", T::RULE_NAME, e);
39 eprintln!("Using default values for rule {}.", T::RULE_NAME);
40 eprintln!("Hint: Check the documentation for valid configuration values.");
41
42 None
43 }
44 }
45 })
46 .unwrap_or_default()
47}
48
49pub fn json_to_toml_value(json_val: &serde_json::Value) -> Option<toml::Value> {
51 match json_val {
52 serde_json::Value::Null => None,
53 serde_json::Value::Bool(b) => Some(toml::Value::Boolean(*b)),
54 serde_json::Value::Number(n) => {
55 if let Some(i) = n.as_i64() {
56 Some(toml::Value::Integer(i))
57 } else {
58 n.as_f64().map(toml::Value::Float)
59 }
60 }
61 serde_json::Value::String(s) => Some(toml::Value::String(s.clone())),
62 serde_json::Value::Array(arr) => {
63 let toml_arr: Vec<_> = arr.iter().filter_map(json_to_toml_value).collect();
64 Some(toml::Value::Array(toml_arr))
65 }
66 serde_json::Value::Object(obj) => {
67 let mut toml_table = toml::map::Map::new();
68 for (k, v) in obj {
69 if let Some(toml_v) = json_to_toml_value(v) {
70 toml_table.insert(k.clone(), toml_v);
71 }
72 }
73 Some(toml::Value::Table(toml_table))
74 }
75 }
76}
77
78#[cfg(test)]
79fn toml_value_to_json(toml_val: &toml::Value) -> Option<serde_json::Value> {
81 match toml_val {
82 toml::Value::String(s) => Some(serde_json::Value::String(s.clone())),
83 toml::Value::Integer(i) => Some(serde_json::json!(i)),
84 toml::Value::Float(f) => Some(serde_json::json!(f)),
85 toml::Value::Boolean(b) => Some(serde_json::Value::Bool(*b)),
86 toml::Value::Array(arr) => {
87 let json_arr: Vec<_> = arr.iter().filter_map(toml_value_to_json).collect();
88 Some(serde_json::Value::Array(json_arr))
89 }
90 toml::Value::Table(table) => {
91 let mut json_obj = serde_json::Map::new();
92 for (k, v) in table {
93 if let Some(json_v) = toml_value_to_json(v) {
94 json_obj.insert(k.clone(), json_v);
95 }
96 }
97 Some(serde_json::Value::Object(json_obj))
98 }
99 toml::Value::Datetime(_) => None, }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use serde::{Deserialize, Serialize};
107 use std::collections::BTreeMap;
108
109 #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
111 #[serde(default)]
112 struct TestRuleConfig {
113 #[serde(default)]
114 enabled: bool,
115 #[serde(default)]
116 indent: i64,
117 #[serde(default)]
118 style: String,
119 #[serde(default)]
120 items: Vec<String>,
121 }
122
123 impl RuleConfig for TestRuleConfig {
124 const RULE_NAME: &'static str = "TEST001";
125 }
126
127 #[test]
128 fn test_toml_value_to_json_basic_types() {
129 let toml_str = toml::Value::String("hello".to_string());
131 let json_str = toml_value_to_json(&toml_str).unwrap();
132 assert_eq!(json_str, serde_json::Value::String("hello".to_string()));
133
134 let toml_int = toml::Value::Integer(42);
136 let json_int = toml_value_to_json(&toml_int).unwrap();
137 assert_eq!(json_int, serde_json::json!(42));
138
139 let toml_float = toml::Value::Float(1.234);
141 let json_float = toml_value_to_json(&toml_float).unwrap();
142 assert_eq!(json_float, serde_json::json!(1.234));
143
144 let toml_bool = toml::Value::Boolean(true);
146 let json_bool = toml_value_to_json(&toml_bool).unwrap();
147 assert_eq!(json_bool, serde_json::Value::Bool(true));
148 }
149
150 #[test]
151 fn test_toml_value_to_json_complex_types() {
152 let toml_arr = toml::Value::Array(vec![
154 toml::Value::String("a".to_string()),
155 toml::Value::String("b".to_string()),
156 ]);
157 let json_arr = toml_value_to_json(&toml_arr).unwrap();
158 assert_eq!(json_arr, serde_json::json!(["a", "b"]));
159
160 let mut toml_table = toml::map::Map::new();
162 toml_table.insert("key1".to_string(), toml::Value::String("value1".to_string()));
163 toml_table.insert("key2".to_string(), toml::Value::Integer(123));
164 let toml_tbl = toml::Value::Table(toml_table);
165 let json_tbl = toml_value_to_json(&toml_tbl).unwrap();
166
167 let expected = serde_json::json!({
168 "key1": "value1",
169 "key2": 123
170 });
171 assert_eq!(json_tbl, expected);
172 }
173
174 #[test]
175 fn test_toml_value_to_json_datetime() {
176 let toml_dt = toml::Value::Datetime("2023-01-01T00:00:00Z".parse().unwrap());
178 assert!(toml_value_to_json(&toml_dt).is_none());
179 }
180
181 #[test]
182 fn test_json_to_toml_value_basic_types() {
183 assert!(json_to_toml_value(&serde_json::Value::Null).is_none());
185
186 let json_bool = serde_json::Value::Bool(false);
188 let toml_bool = json_to_toml_value(&json_bool).unwrap();
189 assert_eq!(toml_bool, toml::Value::Boolean(false));
190
191 let json_int = serde_json::json!(42);
193 let toml_int = json_to_toml_value(&json_int).unwrap();
194 assert_eq!(toml_int, toml::Value::Integer(42));
195
196 let json_float = serde_json::json!(1.234);
198 let toml_float = json_to_toml_value(&json_float).unwrap();
199 assert_eq!(toml_float, toml::Value::Float(1.234));
200
201 let json_str = serde_json::Value::String("test".to_string());
203 let toml_str = json_to_toml_value(&json_str).unwrap();
204 assert_eq!(toml_str, toml::Value::String("test".to_string()));
205 }
206
207 #[test]
208 fn test_json_to_toml_value_complex_types() {
209 let json_arr = serde_json::json!(["x", "y", "z"]);
211 let toml_arr = json_to_toml_value(&json_arr).unwrap();
212 if let toml::Value::Array(arr) = toml_arr {
213 assert_eq!(arr.len(), 3);
214 assert_eq!(arr[0], toml::Value::String("x".to_string()));
215 assert_eq!(arr[1], toml::Value::String("y".to_string()));
216 assert_eq!(arr[2], toml::Value::String("z".to_string()));
217 } else {
218 panic!("Expected array");
219 }
220
221 let json_obj = serde_json::json!({
223 "name": "test",
224 "count": 10,
225 "active": true
226 });
227 let toml_obj = json_to_toml_value(&json_obj).unwrap();
228 if let toml::Value::Table(table) = toml_obj {
229 assert_eq!(table.get("name"), Some(&toml::Value::String("test".to_string())));
230 assert_eq!(table.get("count"), Some(&toml::Value::Integer(10)));
231 assert_eq!(table.get("active"), Some(&toml::Value::Boolean(true)));
232 } else {
233 panic!("Expected table");
234 }
235 }
236
237 #[test]
238 fn test_load_rule_config_default() {
239 let config = crate::config::Config::default();
241
242 let rule_config: TestRuleConfig = load_rule_config(&config);
244 assert_eq!(rule_config, TestRuleConfig::default());
245 }
246
247 #[test]
248 fn test_load_rule_config_with_values() {
249 let mut config = crate::config::Config::default();
251 let mut rule_values = BTreeMap::new();
252 rule_values.insert("enabled".to_string(), toml::Value::Boolean(true));
253 rule_values.insert("indent".to_string(), toml::Value::Integer(4));
254 rule_values.insert("style".to_string(), toml::Value::String("consistent".to_string()));
255 rule_values.insert(
256 "items".to_string(),
257 toml::Value::Array(vec![
258 toml::Value::String("item1".to_string()),
259 toml::Value::String("item2".to_string()),
260 ]),
261 );
262
263 config.rules.insert(
264 "TEST001".to_string(),
265 crate::config::RuleConfig {
266 severity: None,
267 values: rule_values,
268 },
269 );
270
271 let rule_config: TestRuleConfig = load_rule_config(&config);
273 assert!(rule_config.enabled);
274 assert_eq!(rule_config.indent, 4);
275 assert_eq!(rule_config.style, "consistent");
276 assert_eq!(rule_config.items, vec!["item1", "item2"]);
277 }
278
279 #[test]
280 fn test_load_rule_config_partial() {
281 let mut config = crate::config::Config::default();
283 let mut rule_values = BTreeMap::new();
284 rule_values.insert("enabled".to_string(), toml::Value::Boolean(true));
285 rule_values.insert("style".to_string(), toml::Value::String("custom".to_string()));
286
287 config.rules.insert(
288 "TEST001".to_string(),
289 crate::config::RuleConfig {
290 severity: None,
291 values: rule_values,
292 },
293 );
294
295 let rule_config: TestRuleConfig = load_rule_config(&config);
297 assert!(rule_config.enabled); assert_eq!(rule_config.indent, 0); assert_eq!(rule_config.style, "custom"); assert_eq!(rule_config.items, Vec::<String>::new()); }
302
303 #[test]
304 fn test_conversion_roundtrip() {
305 let original = toml::Value::Table({
307 let mut table = toml::map::Map::new();
308 table.insert("string".to_string(), toml::Value::String("test".to_string()));
309 table.insert("number".to_string(), toml::Value::Integer(42));
310 table.insert("bool".to_string(), toml::Value::Boolean(true));
311 table.insert(
312 "array".to_string(),
313 toml::Value::Array(vec![
314 toml::Value::String("a".to_string()),
315 toml::Value::String("b".to_string()),
316 ]),
317 );
318 table
319 });
320
321 let json = toml_value_to_json(&original).unwrap();
322 let back_to_toml = json_to_toml_value(&json).unwrap();
323
324 assert_eq!(original, back_to_toml);
325 }
326
327 #[test]
328 fn test_edge_cases() {
329 let empty_arr = toml::Value::Array(vec![]);
331 let json_arr = toml_value_to_json(&empty_arr).unwrap();
332 assert_eq!(json_arr, serde_json::json!([]));
333
334 let empty_table = toml::Value::Table(toml::map::Map::new());
336 let json_table = toml_value_to_json(&empty_table).unwrap();
337 assert_eq!(json_table, serde_json::json!({}));
338
339 let nested = toml::Value::Table({
341 let mut outer = toml::map::Map::new();
342 outer.insert(
343 "inner".to_string(),
344 toml::Value::Table({
345 let mut inner = toml::map::Map::new();
346 inner.insert("value".to_string(), toml::Value::Integer(123));
347 inner
348 }),
349 );
350 outer
351 });
352 let json_nested = toml_value_to_json(&nested).unwrap();
353 assert_eq!(
354 json_nested,
355 serde_json::json!({
356 "inner": {
357 "value": 123
358 }
359 })
360 );
361 }
362
363 #[test]
364 fn test_float_edge_cases() {
365 let nan = serde_json::Number::from_f64(f64::NAN);
367 assert!(nan.is_none());
368
369 let inf = serde_json::Number::from_f64(f64::INFINITY);
370 assert!(inf.is_none());
371
372 let valid_float = toml::Value::Float(1.23);
374 let json_float = toml_value_to_json(&valid_float).unwrap();
375 assert_eq!(json_float, serde_json::json!(1.23));
376 }
377
378 #[test]
379 fn test_invalid_config_returns_default() {
380 let mut config = crate::config::Config::default();
382 let mut rule_values = BTreeMap::new();
383 rule_values.insert("unknown_field".to_string(), toml::Value::Boolean(true));
384 rule_values.insert("items".to_string(), toml::Value::Table(toml::map::Map::new()));
386
387 config.rules.insert(
388 "TEST001".to_string(),
389 crate::config::RuleConfig {
390 severity: None,
391 values: rule_values,
392 },
393 );
394
395 let rule_config: TestRuleConfig = load_rule_config(&config);
397 assert_eq!(rule_config, TestRuleConfig::default());
399 }
400
401 #[test]
402 fn test_invalid_field_type() {
403 let mut config = crate::config::Config::default();
405 let mut rule_values = BTreeMap::new();
406 rule_values.insert("indent".to_string(), toml::Value::String("not_a_number".to_string()));
408
409 config.rules.insert(
410 "TEST001".to_string(),
411 crate::config::RuleConfig {
412 severity: None,
413 values: rule_values,
414 },
415 );
416
417 let rule_config: TestRuleConfig = load_rule_config(&config);
419 assert_eq!(rule_config, TestRuleConfig::default());
420 }
421}