rumdl_lib/
rule_config.rs

1/// Configuration helper trait and utilities for rules
2///
3/// This module provides utilities to reduce boilerplate in rule configuration
4use toml::Value;
5
6/// Helper macro to implement default_config_section for rules
7///
8/// Usage:
9/// ```ignore
10/// fn default_config_section(&self) -> Option<(String, toml::Value)> {
11///     impl_default_config!(
12///         self.name(),
13///         field1: self.field1,
14///         field2: self.field2,
15///     )
16/// }
17/// ```
18#[macro_export]
19macro_rules! impl_default_config {
20    ($rule_name:expr, $($field:ident: $value:expr),* $(,)?) => {{
21        let mut map = toml::map::Map::new();
22        $(
23            map.insert(stringify!($field).to_string(), $value);
24        )*
25        Some(($rule_name.to_string(), toml::Value::Table(map)))
26    }};
27    ($rule_name:expr $(,)?) => {{
28        let map = toml::map::Map::new();
29        Some(($rule_name.to_string(), toml::Value::Table(map)))
30    }};
31}
32
33/// Simpler helpers for creating TOML values
34pub fn toml_bool(b: bool) -> Value {
35    Value::Boolean(b)
36}
37
38pub fn toml_int<T: Into<i64>>(i: T) -> Value {
39    Value::Integer(i.into())
40}
41
42pub fn toml_string<T: Into<String>>(s: T) -> Value {
43    Value::String(s.into())
44}
45
46pub fn toml_array<T: Into<String>>(items: Vec<T>) -> Value {
47    Value::Array(items.into_iter().map(|s| Value::String(s.into())).collect())
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn test_toml_bool() {
56        assert_eq!(toml_bool(true), Value::Boolean(true));
57        assert_eq!(toml_bool(false), Value::Boolean(false));
58    }
59
60    #[test]
61    fn test_toml_int() {
62        assert_eq!(toml_int(42), Value::Integer(42));
63        assert_eq!(toml_int(0), Value::Integer(0));
64        assert_eq!(toml_int(-10), Value::Integer(-10));
65
66        // Test with different integer types
67        assert_eq!(toml_int(42u8), Value::Integer(42));
68        assert_eq!(toml_int(42u16), Value::Integer(42));
69        assert_eq!(toml_int(42u32), Value::Integer(42));
70        assert_eq!(toml_int(42i8), Value::Integer(42));
71        assert_eq!(toml_int(42i16), Value::Integer(42));
72        assert_eq!(toml_int(42i32), Value::Integer(42));
73    }
74
75    #[test]
76    fn test_toml_string() {
77        assert_eq!(toml_string("hello"), Value::String("hello".to_string()));
78        assert_eq!(toml_string(String::from("world")), Value::String("world".to_string()));
79        assert_eq!(toml_string(""), Value::String("".to_string()));
80
81        // Test unicode
82        assert_eq!(toml_string("你好"), Value::String("你好".to_string()));
83        assert_eq!(toml_string("🦀"), Value::String("🦀".to_string()));
84    }
85
86    #[test]
87    fn test_toml_array() {
88        // Test with string literals
89        let arr1 = toml_array(vec!["one", "two", "three"]);
90        if let Value::Array(values) = arr1 {
91            assert_eq!(values.len(), 3);
92            assert_eq!(values[0], Value::String("one".to_string()));
93            assert_eq!(values[1], Value::String("two".to_string()));
94            assert_eq!(values[2], Value::String("three".to_string()));
95        } else {
96            panic!("Expected array");
97        }
98
99        // Test with String objects
100        let arr2 = toml_array(vec![String::from("alpha"), String::from("beta")]);
101        if let Value::Array(values) = arr2 {
102            assert_eq!(values.len(), 2);
103            assert_eq!(values[0], Value::String("alpha".to_string()));
104            assert_eq!(values[1], Value::String("beta".to_string()));
105        } else {
106            panic!("Expected array");
107        }
108
109        // Test empty array
110        let arr3 = toml_array(Vec::<String>::new());
111        assert_eq!(arr3, Value::Array(vec![]));
112    }
113
114    #[test]
115    fn test_impl_default_config_macro() {
116        // Test the macro with different field types
117        let config = impl_default_config!(
118            "MD001",
119            enabled: toml_bool(true),
120            indent: toml_int(4),
121            style: toml_string("consistent"),
122        );
123
124        assert!(config.is_some());
125        let (rule_name, value) = config.unwrap();
126        assert_eq!(rule_name, "MD001");
127
128        if let Value::Table(table) = value {
129            assert_eq!(table.get("enabled"), Some(&Value::Boolean(true)));
130            assert_eq!(table.get("indent"), Some(&Value::Integer(4)));
131            assert_eq!(table.get("style"), Some(&Value::String("consistent".to_string())));
132        } else {
133            panic!("Expected table");
134        }
135    }
136
137    #[test]
138    fn test_impl_default_config_macro_empty() {
139        // Test macro with no fields (using MD023 which has no config)
140        let config = impl_default_config!("MD023");
141
142        assert!(config.is_some());
143        let (rule_name, value) = config.unwrap();
144        assert_eq!(rule_name, "MD023");
145
146        if let Value::Table(table) = value {
147            assert!(table.is_empty());
148        } else {
149            panic!("Expected table");
150        }
151    }
152
153    #[test]
154    fn test_impl_default_config_macro_complex() {
155        // Test with more complex expressions
156        let my_array = vec!["item1", "item2"];
157        let my_bool = false;
158        let my_number = 42 + 58;
159
160        let config = impl_default_config!(
161            "MD003",
162            items: toml_array(my_array),
163            enabled: toml_bool(my_bool),
164            count: toml_int(my_number),
165        );
166
167        assert!(config.is_some());
168        let (rule_name, value) = config.unwrap();
169        assert_eq!(rule_name, "MD003");
170
171        if let Value::Table(table) = value {
172            assert!(table.contains_key("items"));
173            assert_eq!(table.get("enabled"), Some(&Value::Boolean(false)));
174            assert_eq!(table.get("count"), Some(&Value::Integer(100)));
175        } else {
176            panic!("Expected table");
177        }
178    }
179
180    #[test]
181    fn test_macro_field_name_preservation() {
182        // Ensure field names are correctly stringified
183        let config = impl_default_config!(
184            "MD004",
185            snake_case_field: toml_string("value1"),
186            camelCaseField: toml_string("value2"),
187            UPPERCASE_FIELD: toml_string("value3"),
188        );
189
190        let (_, value) = config.unwrap();
191        if let Value::Table(table) = value {
192            assert!(table.contains_key("snake_case_field"));
193            assert!(table.contains_key("camelCaseField"));
194            assert!(table.contains_key("UPPERCASE_FIELD"));
195        } else {
196            panic!("Expected table");
197        }
198    }
199
200    #[test]
201    fn test_toml_value_types() {
202        // Verify that our helper functions produce the correct TOML value types
203        assert!(matches!(toml_bool(true), Value::Boolean(_)));
204        assert!(matches!(toml_int(42), Value::Integer(_)));
205        assert!(matches!(toml_string("test"), Value::String(_)));
206        assert!(matches!(toml_array(vec!["a", "b"]), Value::Array(_)));
207    }
208
209    #[test]
210    fn test_edge_cases() {
211        // Test with maximum/minimum values for i32
212        assert_eq!(toml_int(i32::MAX), Value::Integer(i32::MAX as i64));
213        assert_eq!(toml_int(i32::MIN), Value::Integer(i32::MIN as i64));
214
215        // Test with special strings
216        assert_eq!(toml_string("with\nnewline"), Value::String("with\nnewline".to_string()));
217        assert_eq!(toml_string("with\ttab"), Value::String("with\ttab".to_string()));
218        assert_eq!(toml_string("with\"quote"), Value::String("with\"quote".to_string()));
219    }
220}