1use std::collections::HashMap;
2use std::path::PathBuf;
3
4use serde::Deserialize;
5
6use crate::facts::FactSpec;
7use crate::level::Level;
8
9#[derive(Debug, Clone, Deserialize, Default)]
11#[serde(deny_unknown_fields)]
12pub struct Config {
13 pub version: u32,
14 #[serde(default)]
22 pub extends: Vec<String>,
23 #[serde(default)]
24 pub ignore: Vec<String>,
25 #[serde(default = "default_respect_gitignore")]
26 pub respect_gitignore: bool,
27 #[serde(default)]
30 pub vars: HashMap<String, String>,
31 #[serde(default)]
34 pub facts: Vec<FactSpec>,
35 #[serde(default)]
36 pub rules: Vec<RuleSpec>,
37 #[serde(default = "default_fix_size_limit")]
46 pub fix_size_limit: Option<u64>,
47 #[serde(default)]
59 pub nested_configs: bool,
60}
61
62#[allow(clippy::unnecessary_wraps)]
68fn default_fix_size_limit() -> Option<u64> {
69 Some(1 << 20)
70}
71
72fn default_respect_gitignore() -> bool {
73 true
74}
75
76impl Config {
77 pub const CURRENT_VERSION: u32 = 1;
78}
79
80#[derive(Debug, Clone, Deserialize)]
85#[serde(untagged)]
86pub enum PathsSpec {
87 Single(String),
88 Many(Vec<String>),
89 IncludeExclude {
90 #[serde(default, deserialize_with = "string_or_vec")]
91 include: Vec<String>,
92 #[serde(default, deserialize_with = "string_or_vec")]
93 exclude: Vec<String>,
94 },
95}
96
97fn string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
98where
99 D: serde::Deserializer<'de>,
100{
101 #[derive(Deserialize)]
102 #[serde(untagged)]
103 enum OneOrMany {
104 One(String),
105 Many(Vec<String>),
106 }
107 match OneOrMany::deserialize(deserializer)? {
108 OneOrMany::One(s) => Ok(vec![s]),
109 OneOrMany::Many(v) => Ok(v),
110 }
111}
112
113#[derive(Debug, Clone, Deserialize)]
116pub struct RuleSpec {
117 pub id: String,
118 pub kind: String,
119 pub level: Level,
120 #[serde(default)]
121 pub paths: Option<PathsSpec>,
122 #[serde(default)]
123 pub message: Option<String>,
124 #[serde(default)]
125 pub policy_url: Option<String>,
126 #[serde(default)]
127 pub when: Option<String>,
128 #[serde(default)]
133 pub fix: Option<FixSpec>,
134 #[serde(flatten)]
137 pub extra: serde_yaml_ng::Mapping,
138}
139
140#[derive(Debug, Clone, Deserialize)]
143#[serde(untagged)]
144pub enum FixSpec {
145 FileCreate {
146 file_create: FileCreateFixSpec,
147 },
148 FileRemove {
149 file_remove: FileRemoveFixSpec,
150 },
151 FilePrepend {
152 file_prepend: FilePrependFixSpec,
153 },
154 FileAppend {
155 file_append: FileAppendFixSpec,
156 },
157 FileRename {
158 file_rename: FileRenameFixSpec,
159 },
160 FileTrimTrailingWhitespace {
161 file_trim_trailing_whitespace: FileTrimTrailingWhitespaceFixSpec,
162 },
163 FileAppendFinalNewline {
164 file_append_final_newline: FileAppendFinalNewlineFixSpec,
165 },
166 FileNormalizeLineEndings {
167 file_normalize_line_endings: FileNormalizeLineEndingsFixSpec,
168 },
169 FileStripBidi {
170 file_strip_bidi: FileStripBidiFixSpec,
171 },
172 FileStripZeroWidth {
173 file_strip_zero_width: FileStripZeroWidthFixSpec,
174 },
175 FileStripBom {
176 file_strip_bom: FileStripBomFixSpec,
177 },
178 FileCollapseBlankLines {
179 file_collapse_blank_lines: FileCollapseBlankLinesFixSpec,
180 },
181}
182
183impl FixSpec {
184 pub fn op_name(&self) -> &'static str {
186 match self {
187 Self::FileCreate { .. } => "file_create",
188 Self::FileRemove { .. } => "file_remove",
189 Self::FilePrepend { .. } => "file_prepend",
190 Self::FileAppend { .. } => "file_append",
191 Self::FileRename { .. } => "file_rename",
192 Self::FileTrimTrailingWhitespace { .. } => "file_trim_trailing_whitespace",
193 Self::FileAppendFinalNewline { .. } => "file_append_final_newline",
194 Self::FileNormalizeLineEndings { .. } => "file_normalize_line_endings",
195 Self::FileStripBidi { .. } => "file_strip_bidi",
196 Self::FileStripZeroWidth { .. } => "file_strip_zero_width",
197 Self::FileStripBom { .. } => "file_strip_bom",
198 Self::FileCollapseBlankLines { .. } => "file_collapse_blank_lines",
199 }
200 }
201}
202
203#[derive(Debug, Clone, Deserialize)]
204#[serde(deny_unknown_fields)]
205pub struct FileCreateFixSpec {
206 pub content: String,
209 #[serde(default)]
213 pub path: Option<PathBuf>,
214 #[serde(default = "default_create_parents")]
216 pub create_parents: bool,
217}
218
219fn default_create_parents() -> bool {
220 true
221}
222
223#[derive(Debug, Clone, Deserialize, Default)]
224#[serde(deny_unknown_fields)]
225pub struct FileRemoveFixSpec {}
226
227#[derive(Debug, Clone, Deserialize)]
228#[serde(deny_unknown_fields)]
229pub struct FilePrependFixSpec {
230 pub content: String,
233}
234
235#[derive(Debug, Clone, Deserialize)]
236#[serde(deny_unknown_fields)]
237pub struct FileAppendFixSpec {
238 pub content: String,
241}
242
243#[derive(Debug, Clone, Deserialize, Default)]
247#[serde(deny_unknown_fields)]
248pub struct FileRenameFixSpec {}
249
250#[derive(Debug, Clone, Deserialize, Default)]
253#[serde(deny_unknown_fields)]
254pub struct FileTrimTrailingWhitespaceFixSpec {}
255
256#[derive(Debug, Clone, Deserialize, Default)]
259#[serde(deny_unknown_fields)]
260pub struct FileAppendFinalNewlineFixSpec {}
261
262#[derive(Debug, Clone, Deserialize, Default)]
265#[serde(deny_unknown_fields)]
266pub struct FileNormalizeLineEndingsFixSpec {}
267
268#[derive(Debug, Clone, Deserialize, Default)]
271#[serde(deny_unknown_fields)]
272pub struct FileStripBidiFixSpec {}
273
274#[derive(Debug, Clone, Deserialize, Default)]
279#[serde(deny_unknown_fields)]
280pub struct FileStripZeroWidthFixSpec {}
281
282#[derive(Debug, Clone, Deserialize, Default)]
285#[serde(deny_unknown_fields)]
286pub struct FileStripBomFixSpec {}
287
288#[derive(Debug, Clone, Deserialize, Default)]
291#[serde(deny_unknown_fields)]
292pub struct FileCollapseBlankLinesFixSpec {}
293
294impl RuleSpec {
295 pub fn deserialize_options<T>(&self) -> crate::error::Result<T>
300 where
301 T: serde::de::DeserializeOwned,
302 {
303 Ok(serde_yaml_ng::from_value(serde_yaml_ng::Value::Mapping(
304 self.extra.clone(),
305 ))?)
306 }
307}
308
309#[derive(Debug, Clone, Deserialize)]
314pub struct NestedRuleSpec {
315 pub kind: String,
316 #[serde(default)]
317 pub paths: Option<PathsSpec>,
318 #[serde(default)]
319 pub message: Option<String>,
320 #[serde(default)]
321 pub policy_url: Option<String>,
322 #[serde(default)]
323 pub when: Option<String>,
324 #[serde(flatten)]
325 pub extra: serde_yaml_ng::Mapping,
326}
327
328impl NestedRuleSpec {
329 pub fn instantiate(
334 &self,
335 parent_id: &str,
336 idx: usize,
337 level: Level,
338 tokens: &crate::template::PathTokens,
339 ) -> RuleSpec {
340 RuleSpec {
341 id: format!("{parent_id}.require[{idx}]"),
342 kind: self.kind.clone(),
343 level,
344 paths: self
345 .paths
346 .as_ref()
347 .map(|p| crate::template::render_paths_spec(p, tokens)),
348 message: self
349 .message
350 .as_deref()
351 .map(|m| crate::template::render_path(m, tokens)),
352 policy_url: self.policy_url.clone(),
353 when: self.when.clone(),
354 fix: None,
355 extra: crate::template::render_mapping(self.extra.clone(), tokens),
356 }
357 }
358}