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}
48
49#[allow(clippy::unnecessary_wraps)]
55fn default_fix_size_limit() -> Option<u64> {
56 Some(1 << 20)
57}
58
59fn default_respect_gitignore() -> bool {
60 true
61}
62
63impl Config {
64 pub const CURRENT_VERSION: u32 = 1;
65}
66
67#[derive(Debug, Clone, Deserialize)]
72#[serde(untagged)]
73pub enum PathsSpec {
74 Single(String),
75 Many(Vec<String>),
76 IncludeExclude {
77 #[serde(default, deserialize_with = "string_or_vec")]
78 include: Vec<String>,
79 #[serde(default, deserialize_with = "string_or_vec")]
80 exclude: Vec<String>,
81 },
82}
83
84fn string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
85where
86 D: serde::Deserializer<'de>,
87{
88 #[derive(Deserialize)]
89 #[serde(untagged)]
90 enum OneOrMany {
91 One(String),
92 Many(Vec<String>),
93 }
94 match OneOrMany::deserialize(deserializer)? {
95 OneOrMany::One(s) => Ok(vec![s]),
96 OneOrMany::Many(v) => Ok(v),
97 }
98}
99
100#[derive(Debug, Clone, Deserialize)]
103pub struct RuleSpec {
104 pub id: String,
105 pub kind: String,
106 pub level: Level,
107 #[serde(default)]
108 pub paths: Option<PathsSpec>,
109 #[serde(default)]
110 pub message: Option<String>,
111 #[serde(default)]
112 pub policy_url: Option<String>,
113 #[serde(default)]
114 pub when: Option<String>,
115 #[serde(default)]
120 pub fix: Option<FixSpec>,
121 #[serde(flatten)]
124 pub extra: serde_yaml_ng::Mapping,
125}
126
127#[derive(Debug, Clone, Deserialize)]
130#[serde(untagged)]
131pub enum FixSpec {
132 FileCreate {
133 file_create: FileCreateFixSpec,
134 },
135 FileRemove {
136 file_remove: FileRemoveFixSpec,
137 },
138 FilePrepend {
139 file_prepend: FilePrependFixSpec,
140 },
141 FileAppend {
142 file_append: FileAppendFixSpec,
143 },
144 FileRename {
145 file_rename: FileRenameFixSpec,
146 },
147 FileTrimTrailingWhitespace {
148 file_trim_trailing_whitespace: FileTrimTrailingWhitespaceFixSpec,
149 },
150 FileAppendFinalNewline {
151 file_append_final_newline: FileAppendFinalNewlineFixSpec,
152 },
153 FileNormalizeLineEndings {
154 file_normalize_line_endings: FileNormalizeLineEndingsFixSpec,
155 },
156 FileStripBidi {
157 file_strip_bidi: FileStripBidiFixSpec,
158 },
159 FileStripZeroWidth {
160 file_strip_zero_width: FileStripZeroWidthFixSpec,
161 },
162 FileStripBom {
163 file_strip_bom: FileStripBomFixSpec,
164 },
165 FileCollapseBlankLines {
166 file_collapse_blank_lines: FileCollapseBlankLinesFixSpec,
167 },
168}
169
170impl FixSpec {
171 pub fn op_name(&self) -> &'static str {
173 match self {
174 Self::FileCreate { .. } => "file_create",
175 Self::FileRemove { .. } => "file_remove",
176 Self::FilePrepend { .. } => "file_prepend",
177 Self::FileAppend { .. } => "file_append",
178 Self::FileRename { .. } => "file_rename",
179 Self::FileTrimTrailingWhitespace { .. } => "file_trim_trailing_whitespace",
180 Self::FileAppendFinalNewline { .. } => "file_append_final_newline",
181 Self::FileNormalizeLineEndings { .. } => "file_normalize_line_endings",
182 Self::FileStripBidi { .. } => "file_strip_bidi",
183 Self::FileStripZeroWidth { .. } => "file_strip_zero_width",
184 Self::FileStripBom { .. } => "file_strip_bom",
185 Self::FileCollapseBlankLines { .. } => "file_collapse_blank_lines",
186 }
187 }
188}
189
190#[derive(Debug, Clone, Deserialize)]
191#[serde(deny_unknown_fields)]
192pub struct FileCreateFixSpec {
193 pub content: String,
196 #[serde(default)]
200 pub path: Option<PathBuf>,
201 #[serde(default = "default_create_parents")]
203 pub create_parents: bool,
204}
205
206fn default_create_parents() -> bool {
207 true
208}
209
210#[derive(Debug, Clone, Deserialize, Default)]
211#[serde(deny_unknown_fields)]
212pub struct FileRemoveFixSpec {}
213
214#[derive(Debug, Clone, Deserialize)]
215#[serde(deny_unknown_fields)]
216pub struct FilePrependFixSpec {
217 pub content: String,
220}
221
222#[derive(Debug, Clone, Deserialize)]
223#[serde(deny_unknown_fields)]
224pub struct FileAppendFixSpec {
225 pub content: String,
228}
229
230#[derive(Debug, Clone, Deserialize, Default)]
234#[serde(deny_unknown_fields)]
235pub struct FileRenameFixSpec {}
236
237#[derive(Debug, Clone, Deserialize, Default)]
240#[serde(deny_unknown_fields)]
241pub struct FileTrimTrailingWhitespaceFixSpec {}
242
243#[derive(Debug, Clone, Deserialize, Default)]
246#[serde(deny_unknown_fields)]
247pub struct FileAppendFinalNewlineFixSpec {}
248
249#[derive(Debug, Clone, Deserialize, Default)]
252#[serde(deny_unknown_fields)]
253pub struct FileNormalizeLineEndingsFixSpec {}
254
255#[derive(Debug, Clone, Deserialize, Default)]
258#[serde(deny_unknown_fields)]
259pub struct FileStripBidiFixSpec {}
260
261#[derive(Debug, Clone, Deserialize, Default)]
266#[serde(deny_unknown_fields)]
267pub struct FileStripZeroWidthFixSpec {}
268
269#[derive(Debug, Clone, Deserialize, Default)]
272#[serde(deny_unknown_fields)]
273pub struct FileStripBomFixSpec {}
274
275#[derive(Debug, Clone, Deserialize, Default)]
278#[serde(deny_unknown_fields)]
279pub struct FileCollapseBlankLinesFixSpec {}
280
281impl RuleSpec {
282 pub fn deserialize_options<T>(&self) -> crate::error::Result<T>
287 where
288 T: serde::de::DeserializeOwned,
289 {
290 Ok(serde_yaml_ng::from_value(serde_yaml_ng::Value::Mapping(
291 self.extra.clone(),
292 ))?)
293 }
294}
295
296#[derive(Debug, Clone, Deserialize)]
301pub struct NestedRuleSpec {
302 pub kind: String,
303 #[serde(default)]
304 pub paths: Option<PathsSpec>,
305 #[serde(default)]
306 pub message: Option<String>,
307 #[serde(default)]
308 pub policy_url: Option<String>,
309 #[serde(default)]
310 pub when: Option<String>,
311 #[serde(flatten)]
312 pub extra: serde_yaml_ng::Mapping,
313}
314
315impl NestedRuleSpec {
316 pub fn instantiate(
321 &self,
322 parent_id: &str,
323 idx: usize,
324 level: Level,
325 tokens: &crate::template::PathTokens,
326 ) -> RuleSpec {
327 RuleSpec {
328 id: format!("{parent_id}.require[{idx}]"),
329 kind: self.kind.clone(),
330 level,
331 paths: self
332 .paths
333 .as_ref()
334 .map(|p| crate::template::render_paths_spec(p, tokens)),
335 message: self
336 .message
337 .as_deref()
338 .map(|m| crate::template::render_path(m, tokens)),
339 policy_url: self.policy_url.clone(),
340 when: self.when.clone(),
341 fix: None,
342 extra: crate::template::render_mapping(self.extra.clone(), tokens),
343 }
344 }
345}