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)]
11#[serde(deny_unknown_fields)]
12pub struct Config {
13 pub version: u32,
14 #[serde(default)]
15 pub ignore: Vec<String>,
16 #[serde(default = "default_respect_gitignore")]
17 pub respect_gitignore: bool,
18 #[serde(default)]
21 pub vars: HashMap<String, String>,
22 #[serde(default)]
25 pub facts: Vec<FactSpec>,
26 #[serde(default)]
27 pub rules: Vec<RuleSpec>,
28}
29
30fn default_respect_gitignore() -> bool {
31 true
32}
33
34impl Config {
35 pub const CURRENT_VERSION: u32 = 1;
36}
37
38#[derive(Debug, Clone, Deserialize)]
43#[serde(untagged)]
44pub enum PathsSpec {
45 Single(String),
46 Many(Vec<String>),
47 IncludeExclude {
48 #[serde(default, deserialize_with = "string_or_vec")]
49 include: Vec<String>,
50 #[serde(default, deserialize_with = "string_or_vec")]
51 exclude: Vec<String>,
52 },
53}
54
55fn string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
56where
57 D: serde::Deserializer<'de>,
58{
59 #[derive(Deserialize)]
60 #[serde(untagged)]
61 enum OneOrMany {
62 One(String),
63 Many(Vec<String>),
64 }
65 match OneOrMany::deserialize(deserializer)? {
66 OneOrMany::One(s) => Ok(vec![s]),
67 OneOrMany::Many(v) => Ok(v),
68 }
69}
70
71#[derive(Debug, Clone, Deserialize)]
74pub struct RuleSpec {
75 pub id: String,
76 pub kind: String,
77 pub level: Level,
78 #[serde(default)]
79 pub paths: Option<PathsSpec>,
80 #[serde(default)]
81 pub message: Option<String>,
82 #[serde(default)]
83 pub policy_url: Option<String>,
84 #[serde(default)]
85 pub when: Option<String>,
86 #[serde(default)]
91 pub fix: Option<FixSpec>,
92 #[serde(flatten)]
95 pub extra: serde_yaml_ng::Mapping,
96}
97
98#[derive(Debug, Clone, Deserialize)]
101#[serde(untagged)]
102pub enum FixSpec {
103 FileCreate { file_create: FileCreateFixSpec },
104 FileRemove { file_remove: FileRemoveFixSpec },
105 FilePrepend { file_prepend: FilePrependFixSpec },
106 FileAppend { file_append: FileAppendFixSpec },
107 FileRename { file_rename: FileRenameFixSpec },
108}
109
110impl FixSpec {
111 pub fn op_name(&self) -> &'static str {
113 match self {
114 Self::FileCreate { .. } => "file_create",
115 Self::FileRemove { .. } => "file_remove",
116 Self::FilePrepend { .. } => "file_prepend",
117 Self::FileAppend { .. } => "file_append",
118 Self::FileRename { .. } => "file_rename",
119 }
120 }
121}
122
123#[derive(Debug, Clone, Deserialize)]
124#[serde(deny_unknown_fields)]
125pub struct FileCreateFixSpec {
126 pub content: String,
129 #[serde(default)]
133 pub path: Option<PathBuf>,
134 #[serde(default = "default_create_parents")]
136 pub create_parents: bool,
137}
138
139fn default_create_parents() -> bool {
140 true
141}
142
143#[derive(Debug, Clone, Deserialize, Default)]
144#[serde(deny_unknown_fields)]
145pub struct FileRemoveFixSpec {}
146
147#[derive(Debug, Clone, Deserialize)]
148#[serde(deny_unknown_fields)]
149pub struct FilePrependFixSpec {
150 pub content: String,
153}
154
155#[derive(Debug, Clone, Deserialize)]
156#[serde(deny_unknown_fields)]
157pub struct FileAppendFixSpec {
158 pub content: String,
161}
162
163#[derive(Debug, Clone, Deserialize, Default)]
167#[serde(deny_unknown_fields)]
168pub struct FileRenameFixSpec {}
169
170impl RuleSpec {
171 pub fn deserialize_options<T>(&self) -> crate::error::Result<T>
176 where
177 T: serde::de::DeserializeOwned,
178 {
179 Ok(serde_yaml_ng::from_value(serde_yaml_ng::Value::Mapping(
180 self.extra.clone(),
181 ))?)
182 }
183}
184
185#[derive(Debug, Clone, Deserialize)]
190pub struct NestedRuleSpec {
191 pub kind: String,
192 #[serde(default)]
193 pub paths: Option<PathsSpec>,
194 #[serde(default)]
195 pub message: Option<String>,
196 #[serde(default)]
197 pub policy_url: Option<String>,
198 #[serde(default)]
199 pub when: Option<String>,
200 #[serde(flatten)]
201 pub extra: serde_yaml_ng::Mapping,
202}
203
204impl NestedRuleSpec {
205 pub fn instantiate(
210 &self,
211 parent_id: &str,
212 idx: usize,
213 level: Level,
214 tokens: &crate::template::PathTokens,
215 ) -> RuleSpec {
216 RuleSpec {
217 id: format!("{parent_id}.require[{idx}]"),
218 kind: self.kind.clone(),
219 level,
220 paths: self
221 .paths
222 .as_ref()
223 .map(|p| crate::template::render_paths_spec(p, tokens)),
224 message: self
225 .message
226 .as_deref()
227 .map(|m| crate::template::render_path(m, tokens)),
228 policy_url: self.policy_url.clone(),
229 when: self.when.clone(),
230 fix: None,
231 extra: crate::template::render_mapping(self.extra.clone(), tokens),
232 }
233 }
234}