1use serde::{Deserialize, Serialize};
12
13use super::error::{PackParseError, MAX_REQUIRE_DEPTH};
14
15#[non_exhaustive]
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(rename_all = "lowercase")]
22pub enum OsKind {
23 Windows,
25 Linux,
27 Macos,
29}
30
31#[non_exhaustive]
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
40#[serde(rename_all = "lowercase")]
41pub enum RequireOnFail {
42 #[default]
44 Error,
45 Skip,
47 Warn,
49}
50
51#[non_exhaustive]
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
60#[serde(rename_all = "lowercase")]
61pub enum ExecOnFail {
62 #[default]
64 Error,
65 Warn,
67 Ignore,
69}
70
71#[non_exhaustive]
79#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
80#[serde(rename_all = "snake_case")]
81pub enum Predicate {
82 PathExists(String),
84 CmdAvailable(String),
86 RegKey {
90 path: String,
92 name: Option<String>,
94 },
95 Os(OsKind),
97 PsVersion(String),
99 SymlinkOk {
101 src: String,
103 dst: String,
105 },
106 AllOf(Vec<Predicate>),
108 AnyOf(Vec<Predicate>),
110 NoneOf(Vec<Predicate>),
112}
113
114#[non_exhaustive]
120#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
121#[serde(rename_all = "snake_case")]
122pub enum Combiner {
123 AllOf(Vec<Predicate>),
125 AnyOf(Vec<Predicate>),
127 NoneOf(Vec<Predicate>),
129}
130
131impl Predicate {
132 pub fn from_yaml(value: &serde_yaml::Value, depth: usize) -> Result<Self, PackParseError> {
138 let (key, v) = predicate_single_key(value, depth)?;
139 match key.as_str() {
140 "path_exists" => parse_path_exists(v),
141 "cmd_available" => parse_cmd_available(v),
142 "reg_key" => parse_reg_key(v),
143 "os" => parse_os(v),
144 "psversion" => parse_ps_version(v),
145 "symlink_ok" => parse_symlink_ok(v),
146 "all_of" => parse_all_of(v, depth),
147 "any_of" => parse_any_of(v, depth),
148 "none_of" => parse_none_of(v, depth),
149 other => Err(unknown_predicate_err(other)),
150 }
151 }
152}
153
154fn predicate_single_key(
158 value: &serde_yaml::Value,
159 depth: usize,
160) -> Result<(String, &serde_yaml::Value), PackParseError> {
161 if depth >= MAX_REQUIRE_DEPTH {
162 return Err(PackParseError::RequireDepthExceeded { depth, max: MAX_REQUIRE_DEPTH });
163 }
164
165 let mapping = value.as_mapping().ok_or_else(|| PackParseError::InvalidPredicate {
166 detail: "predicate must be a single-key mapping".to_string(),
167 })?;
168
169 if mapping.len() != 1 {
170 return Err(PackParseError::InvalidPredicate {
171 detail: format!("predicate must be a single-key mapping (got {} keys)", mapping.len()),
172 });
173 }
174
175 let (k, v) = mapping.iter().next().expect("len==1 checked above");
176 let key = k.as_str().ok_or_else(|| PackParseError::InvalidPredicate {
177 detail: "predicate key must be a string".to_string(),
178 })?;
179 Ok((key.to_string(), v))
180}
181
182fn parse_path_exists(value: &serde_yaml::Value) -> Result<Predicate, PackParseError> {
183 Ok(Predicate::PathExists(string_arg(value, "path_exists")?))
184}
185
186fn parse_cmd_available(value: &serde_yaml::Value) -> Result<Predicate, PackParseError> {
187 Ok(Predicate::CmdAvailable(string_arg(value, "cmd_available")?))
188}
189
190fn parse_reg_key(value: &serde_yaml::Value) -> Result<Predicate, PackParseError> {
191 Ok(Predicate::RegKey { path: reg_path(value)?, name: reg_name(value)? })
192}
193
194fn parse_os(value: &serde_yaml::Value) -> Result<Predicate, PackParseError> {
195 Ok(Predicate::Os(serde_yaml::from_value::<OsKind>(value.clone())?))
196}
197
198fn parse_ps_version(value: &serde_yaml::Value) -> Result<Predicate, PackParseError> {
199 Ok(Predicate::PsVersion(string_arg(value, "psversion")?))
200}
201
202fn parse_symlink_ok(value: &serde_yaml::Value) -> Result<Predicate, PackParseError> {
203 Ok(Predicate::SymlinkOk {
204 src: map_string(value, "symlink_ok", "src")?,
205 dst: map_string(value, "symlink_ok", "dst")?,
206 })
207}
208
209fn parse_all_of(value: &serde_yaml::Value, depth: usize) -> Result<Predicate, PackParseError> {
210 Ok(Predicate::AllOf(parse_list(value, depth + 1)?))
211}
212
213fn parse_any_of(value: &serde_yaml::Value, depth: usize) -> Result<Predicate, PackParseError> {
214 Ok(Predicate::AnyOf(parse_list(value, depth + 1)?))
215}
216
217fn parse_none_of(value: &serde_yaml::Value, depth: usize) -> Result<Predicate, PackParseError> {
218 Ok(Predicate::NoneOf(parse_list(value, depth + 1)?))
219}
220
221fn unknown_predicate_err(key: &str) -> PackParseError {
222 PackParseError::InvalidPredicate {
223 detail: format!(
224 "unknown predicate {key:?}: valid kinds are path_exists, cmd_available, \
225reg_key, os, psversion, symlink_ok, all_of, any_of, none_of"
226 ),
227 }
228}
229
230impl Combiner {
231 pub fn from_mapping(
238 mapping: &serde_yaml::Mapping,
239 depth: usize,
240 ) -> Result<Self, PackParseError> {
241 let mut seen: Vec<(&'static str, &serde_yaml::Value)> = Vec::new();
242 for key in ["all_of", "any_of", "none_of"] {
243 if let Some(v) = mapping.get(serde_yaml::Value::String(key.to_string())) {
244 seen.push((key, v));
245 }
246 }
247 if seen.len() != 1 {
248 return Err(PackParseError::RequireCombinerArity { count: seen.len() });
249 }
250 let (key, value) = seen[0];
251 let list = parse_list(value, depth + 1)?;
252 Ok(match key {
253 "all_of" => Self::AllOf(list),
254 "any_of" => Self::AnyOf(list),
255 "none_of" => Self::NoneOf(list),
256 _ => unreachable!("iteration set is fixed"),
257 })
258 }
259}
260
261fn parse_list(value: &serde_yaml::Value, depth: usize) -> Result<Vec<Predicate>, PackParseError> {
263 let seq = value.as_sequence().ok_or_else(|| PackParseError::InvalidPredicate {
264 detail: "combiner value must be a sequence of predicate entries".to_string(),
265 })?;
266 seq.iter().map(|v| Predicate::from_yaml(v, depth)).collect()
267}
268
269fn string_arg(value: &serde_yaml::Value, key: &str) -> Result<String, PackParseError> {
270 value.as_str().map(str::to_owned).ok_or_else(|| PackParseError::InvalidPredicate {
271 detail: format!("{key} expects a string argument"),
272 })
273}
274
275fn reg_path(value: &serde_yaml::Value) -> Result<String, PackParseError> {
280 if value.as_str().is_some() {
281 return Err(PackParseError::InvalidPredicate {
282 detail: "reg_key string form is not supported: use { path, name } map".to_string(),
283 });
284 }
285 map_string(value, "reg_key", "path")
286}
287
288fn reg_name(value: &serde_yaml::Value) -> Result<Option<String>, PackParseError> {
289 if value.as_str().is_some() {
290 return Err(PackParseError::InvalidPredicate {
291 detail: "reg_key string form is not supported: use { path, name } map".to_string(),
292 });
293 }
294 match value.as_mapping() {
295 Some(m) => match m.get(serde_yaml::Value::String("name".to_string())) {
296 Some(v) if v.is_null() => Ok(None),
297 Some(v) => v.as_str().map(str::to_owned).map(Some).ok_or_else(|| {
298 PackParseError::InvalidPredicate {
299 detail: "reg_key.name must be a string".to_string(),
300 }
301 }),
302 None => Ok(None),
303 },
304 None => Err(PackParseError::InvalidPredicate {
305 detail: "reg_key expects a { path, name } map".to_string(),
306 }),
307 }
308}
309
310fn map_string(
311 value: &serde_yaml::Value,
312 outer: &str,
313 field: &str,
314) -> Result<String, PackParseError> {
315 let map = value.as_mapping().ok_or_else(|| PackParseError::InvalidPredicate {
316 detail: format!("{outer} expects a mapping argument"),
317 })?;
318 let v = map.get(serde_yaml::Value::String(field.to_string())).ok_or_else(|| {
319 PackParseError::InvalidPredicate {
320 detail: format!("{outer} missing required field {field:?}"),
321 }
322 })?;
323 v.as_str().map(str::to_owned).ok_or_else(|| PackParseError::InvalidPredicate {
324 detail: format!("{outer}.{field} must be a string"),
325 })
326}