Skip to main content

code_baseline/cli/
toml_config.rs

1use crate::config::{RuleConfig, Severity};
2use serde::Deserialize;
3
4/// Top-level TOML config file structure.
5#[derive(Debug, Deserialize)]
6pub struct TomlConfig {
7    pub baseline: BaselineSection,
8    #[serde(default)]
9    pub rule: Vec<TomlRule>,
10}
11
12/// A `[[baseline.scoped]]` entry that applies a preset to a specific directory.
13#[derive(Debug, Clone, Deserialize)]
14pub struct ScopedPreset {
15    #[serde(deserialize_with = "string_or_vec")]
16    pub preset: Vec<String>,
17    pub path: String,
18    #[serde(default)]
19    pub exclude_rules: Vec<String>,
20}
21
22/// Deserialize a TOML value that is either a single string or an array of strings.
23fn string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
24where
25    D: serde::Deserializer<'de>,
26{
27    use serde::de;
28
29    struct StringOrVec;
30
31    impl<'de> de::Visitor<'de> for StringOrVec {
32        type Value = Vec<String>;
33
34        fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
35            f.write_str("a string or array of strings")
36        }
37
38        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
39            Ok(vec![v.to_owned()])
40        }
41
42        fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
43            let mut v = Vec::new();
44            while let Some(s) = seq.next_element()? {
45                v.push(s);
46            }
47            Ok(v)
48        }
49    }
50
51    deserializer.deserialize_any(StringOrVec)
52}
53
54/// The `[baseline]` section.
55#[derive(Debug, Deserialize)]
56pub struct BaselineSection {
57    #[allow(dead_code)]
58    pub name: Option<String>,
59    #[serde(default)]
60    pub include: Vec<String>,
61    #[serde(default)]
62    pub exclude: Vec<String>,
63    #[serde(default)]
64    pub extends: Vec<String>,
65    /// Paths to plugin TOML files containing additional rules
66    #[serde(default)]
67    pub plugins: Vec<String>,
68    /// Scoped presets: apply a preset only to files under a specific path
69    #[serde(default)]
70    pub scoped: Vec<ScopedPreset>,
71}
72
73/// A single `[[rule]]` entry.
74#[derive(Debug, Clone, Deserialize)]
75pub struct TomlRule {
76    pub id: String,
77    #[serde(rename = "type")]
78    pub rule_type: String,
79    #[serde(default = "default_severity")]
80    pub severity: String,
81    pub glob: Option<String>,
82    #[serde(default)]
83    pub message: String,
84    pub suggest: Option<String>,
85    #[serde(default)]
86    pub allowed_classes: Vec<String>,
87    #[serde(default)]
88    pub token_map: Vec<String>,
89    pub pattern: Option<String>,
90    pub max_count: Option<usize>,
91    #[serde(default)]
92    pub packages: Vec<String>,
93    #[serde(default)]
94    pub regex: bool,
95    pub manifest: Option<String>,
96    #[serde(default)]
97    pub exclude_glob: Vec<String>,
98    pub file_contains: Option<String>,
99    pub file_not_contains: Option<String>,
100    #[serde(default)]
101    pub required_files: Vec<String>,
102    #[serde(default)]
103    pub forbidden_files: Vec<String>,
104    pub condition_pattern: Option<String>,
105    #[serde(default)]
106    pub skip_strings: bool,
107}
108
109fn default_severity() -> String {
110    "warning".into()
111}
112
113impl Default for TomlRule {
114    fn default() -> Self {
115        Self {
116            id: String::new(),
117            rule_type: String::new(),
118            severity: default_severity(),
119            glob: None,
120            message: String::new(),
121            suggest: None,
122            allowed_classes: Vec::new(),
123            token_map: Vec::new(),
124            pattern: None,
125            max_count: None,
126            packages: Vec::new(),
127            regex: false,
128            manifest: None,
129            exclude_glob: Vec::new(),
130            file_contains: None,
131            file_not_contains: None,
132            required_files: Vec::new(),
133            forbidden_files: Vec::new(),
134            condition_pattern: None,
135            skip_strings: false,
136        }
137    }
138}
139
140impl TomlRule {
141    /// Convert to the core `RuleConfig` type.
142    pub fn to_rule_config(&self) -> RuleConfig {
143        let severity = match self.severity.to_lowercase().as_str() {
144            "error" => Severity::Error,
145            _ => Severity::Warning,
146        };
147
148        RuleConfig {
149            id: self.id.clone(),
150            severity,
151            message: self.message.clone(),
152            suggest: self.suggest.clone(),
153            glob: self.glob.clone(),
154            allowed_classes: self.allowed_classes.clone(),
155            token_map: self.token_map.clone(),
156            pattern: self.pattern.clone(),
157            max_count: self.max_count,
158            packages: self.packages.clone(),
159            regex: self.regex,
160            manifest: self.manifest.clone(),
161            exclude_glob: self.exclude_glob.clone(),
162            file_contains: self.file_contains.clone(),
163            file_not_contains: self.file_not_contains.clone(),
164            required_files: self.required_files.clone(),
165            forbidden_files: self.forbidden_files.clone(),
166            condition_pattern: self.condition_pattern.clone(),
167            skip_strings: self.skip_strings,
168        }
169    }
170}