lex_config/
rule_config.rs1use serde::{Deserialize, Serialize};
10use std::collections::BTreeMap;
11use std::fmt;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum Severity {
27 Allow,
28 Warn,
29 Deny,
30}
31
32impl Default for Severity {
33 fn default() -> Self {
39 Severity::Warn
40 }
41}
42
43impl Default for RuleConfig {
44 fn default() -> Self {
45 RuleConfig::Bare(Severity::default())
46 }
47}
48
49impl fmt::Display for Severity {
50 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51 f.write_str(match self {
52 Severity::Allow => "allow",
53 Severity::Warn => "warn",
54 Severity::Deny => "deny",
55 })
56 }
57}
58
59pub type RuleOptions = BTreeMap<String, toml::Value>;
64
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
80#[serde(untagged)]
81pub enum RuleConfig {
82 Bare(Severity),
84 WithOptions(Severity, RuleOptions),
86}
87
88impl RuleConfig {
89 pub fn severity(&self) -> Severity {
91 match self {
92 RuleConfig::Bare(s) | RuleConfig::WithOptions(s, _) => *s,
93 }
94 }
95
96 pub fn options(&self) -> Option<&RuleOptions> {
98 match self {
99 RuleConfig::Bare(_) => None,
100 RuleConfig::WithOptions(_, opts) => Some(opts),
101 }
102 }
103}
104
105impl From<Severity> for RuleConfig {
106 fn from(s: Severity) -> Self {
107 RuleConfig::Bare(s)
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[derive(Debug, Deserialize, Serialize)]
116 struct Wrap {
117 rule: RuleConfig,
118 }
119
120 fn parse(toml: &str) -> RuleConfig {
121 toml::from_str::<Wrap>(toml).expect("parse").rule
122 }
123
124 #[test]
125 fn bare_string_warn() {
126 let r = parse(r#"rule = "warn""#);
127 assert_eq!(r.severity(), Severity::Warn);
128 assert!(r.options().is_none());
129 }
130
131 #[test]
132 fn bare_string_allow() {
133 let r = parse(r#"rule = "allow""#);
134 assert_eq!(r.severity(), Severity::Allow);
135 assert!(r.options().is_none());
136 }
137
138 #[test]
139 fn bare_string_deny() {
140 let r = parse(r#"rule = "deny""#);
141 assert_eq!(r.severity(), Severity::Deny);
142 assert!(r.options().is_none());
143 }
144
145 #[test]
146 fn array_form_with_options() {
147 let r = parse(r#"rule = ["warn", { max = 100 }]"#);
148 assert_eq!(r.severity(), Severity::Warn);
149 let opts = r.options().expect("options present");
150 assert_eq!(opts.get("max"), Some(&toml::Value::Integer(100)));
151 }
152
153 #[test]
154 fn array_form_multiple_options() {
155 let r = parse(r#"rule = ["deny", { max = 80, indent = "tabs" }]"#);
156 assert_eq!(r.severity(), Severity::Deny);
157 let opts = r.options().unwrap();
158 assert_eq!(opts.get("max"), Some(&toml::Value::Integer(80)));
159 assert_eq!(
160 opts.get("indent"),
161 Some(&toml::Value::String("tabs".into()))
162 );
163 }
164
165 #[test]
166 fn array_form_empty_options() {
167 let r = parse(r#"rule = ["warn", {}]"#);
168 assert_eq!(r.severity(), Severity::Warn);
169 assert!(r.options().unwrap().is_empty());
170 }
171
172 #[test]
173 fn rejects_invalid_severity_string() {
174 assert!(toml::from_str::<Wrap>(r#"rule = "error""#).is_err());
177 }
178
179 #[test]
180 fn rejects_invalid_array_severity() {
181 assert!(toml::from_str::<Wrap>(r#"rule = ["error", {}]"#).is_err());
182 }
183
184 #[test]
185 fn round_trip_bare() {
186 let r = parse(r#"rule = "warn""#);
187 let s = toml::to_string(&Wrap { rule: r.clone() }).unwrap();
188 let back = toml::from_str::<Wrap>(&s).unwrap().rule;
189 assert_eq!(back, r);
190 }
191
192 #[test]
193 fn round_trip_with_options() {
194 let r = parse(r#"rule = ["warn", { max = 100, indent = "tabs" }]"#);
198 let s = toml::to_string(&Wrap { rule: r.clone() }).unwrap();
199 let back = toml::from_str::<Wrap>(&s).unwrap().rule;
200 assert_eq!(back, r);
201 assert_eq!(back.severity(), Severity::Warn);
202 let opts = back.options().expect("options preserved");
203 assert_eq!(opts.get("max"), Some(&toml::Value::Integer(100)));
204 assert_eq!(
205 opts.get("indent"),
206 Some(&toml::Value::String("tabs".into()))
207 );
208 }
209
210 #[test]
211 fn severity_display() {
212 assert_eq!(Severity::Allow.to_string(), "allow");
213 assert_eq!(Severity::Warn.to_string(), "warn");
214 assert_eq!(Severity::Deny.to_string(), "deny");
215 }
216
217 #[test]
218 fn severity_into_rule_config() {
219 let r: RuleConfig = Severity::Warn.into();
220 assert_eq!(r.severity(), Severity::Warn);
221 assert!(r.options().is_none());
222 }
223}