use serde::{Deserialize, Serialize};
use super::error::{PackParseError, MAX_REQUIRE_DEPTH};
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OsKind {
Windows,
Linux,
Macos,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum RequireOnFail {
#[default]
Error,
Skip,
Warn,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum ExecOnFail {
#[default]
Error,
Warn,
Ignore,
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Predicate {
PathExists(String),
CmdAvailable(String),
RegKey {
path: String,
name: Option<String>,
},
Os(OsKind),
PsVersion(String),
SymlinkOk {
src: String,
dst: String,
},
AllOf(Vec<Predicate>),
AnyOf(Vec<Predicate>),
NoneOf(Vec<Predicate>),
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Combiner {
AllOf(Vec<Predicate>),
AnyOf(Vec<Predicate>),
NoneOf(Vec<Predicate>),
}
impl Predicate {
pub fn from_yaml(value: &serde_yaml::Value, depth: usize) -> Result<Self, PackParseError> {
let (key, v) = predicate_single_key(value, depth)?;
match key.as_str() {
"path_exists" => parse_path_exists(v),
"cmd_available" => parse_cmd_available(v),
"reg_key" => parse_reg_key(v),
"os" => parse_os(v),
"psversion" => parse_ps_version(v),
"symlink_ok" => parse_symlink_ok(v),
"all_of" => parse_all_of(v, depth),
"any_of" => parse_any_of(v, depth),
"none_of" => parse_none_of(v, depth),
other => Err(unknown_predicate_err(other)),
}
}
}
fn predicate_single_key(
value: &serde_yaml::Value,
depth: usize,
) -> Result<(String, &serde_yaml::Value), PackParseError> {
if depth >= MAX_REQUIRE_DEPTH {
return Err(PackParseError::RequireDepthExceeded { depth, max: MAX_REQUIRE_DEPTH });
}
let mapping = value.as_mapping().ok_or_else(|| PackParseError::InvalidPredicate {
detail: "predicate must be a single-key mapping".to_string(),
})?;
if mapping.len() != 1 {
return Err(PackParseError::InvalidPredicate {
detail: format!("predicate must be a single-key mapping (got {} keys)", mapping.len()),
});
}
let (k, v) = mapping.iter().next().expect("len==1 checked above");
let key = k.as_str().ok_or_else(|| PackParseError::InvalidPredicate {
detail: "predicate key must be a string".to_string(),
})?;
Ok((key.to_string(), v))
}
fn parse_path_exists(value: &serde_yaml::Value) -> Result<Predicate, PackParseError> {
Ok(Predicate::PathExists(string_arg(value, "path_exists")?))
}
fn parse_cmd_available(value: &serde_yaml::Value) -> Result<Predicate, PackParseError> {
Ok(Predicate::CmdAvailable(string_arg(value, "cmd_available")?))
}
fn parse_reg_key(value: &serde_yaml::Value) -> Result<Predicate, PackParseError> {
Ok(Predicate::RegKey { path: reg_path(value)?, name: reg_name(value)? })
}
fn parse_os(value: &serde_yaml::Value) -> Result<Predicate, PackParseError> {
Ok(Predicate::Os(serde_yaml::from_value::<OsKind>(value.clone())?))
}
fn parse_ps_version(value: &serde_yaml::Value) -> Result<Predicate, PackParseError> {
Ok(Predicate::PsVersion(string_arg(value, "psversion")?))
}
fn parse_symlink_ok(value: &serde_yaml::Value) -> Result<Predicate, PackParseError> {
Ok(Predicate::SymlinkOk {
src: map_string(value, "symlink_ok", "src")?,
dst: map_string(value, "symlink_ok", "dst")?,
})
}
fn parse_all_of(value: &serde_yaml::Value, depth: usize) -> Result<Predicate, PackParseError> {
Ok(Predicate::AllOf(parse_list(value, depth + 1)?))
}
fn parse_any_of(value: &serde_yaml::Value, depth: usize) -> Result<Predicate, PackParseError> {
Ok(Predicate::AnyOf(parse_list(value, depth + 1)?))
}
fn parse_none_of(value: &serde_yaml::Value, depth: usize) -> Result<Predicate, PackParseError> {
Ok(Predicate::NoneOf(parse_list(value, depth + 1)?))
}
fn unknown_predicate_err(key: &str) -> PackParseError {
PackParseError::InvalidPredicate {
detail: format!(
"unknown predicate {key:?}: valid kinds are path_exists, cmd_available, \
reg_key, os, psversion, symlink_ok, all_of, any_of, none_of"
),
}
}
impl Combiner {
pub fn from_mapping(
mapping: &serde_yaml::Mapping,
depth: usize,
) -> Result<Self, PackParseError> {
let mut seen: Vec<(&'static str, &serde_yaml::Value)> = Vec::new();
for key in ["all_of", "any_of", "none_of"] {
if let Some(v) = mapping.get(serde_yaml::Value::String(key.to_string())) {
seen.push((key, v));
}
}
if seen.len() != 1 {
return Err(PackParseError::RequireCombinerArity { count: seen.len() });
}
let (key, value) = seen[0];
let list = parse_list(value, depth + 1)?;
Ok(match key {
"all_of" => Self::AllOf(list),
"any_of" => Self::AnyOf(list),
"none_of" => Self::NoneOf(list),
_ => unreachable!("iteration set is fixed"),
})
}
}
fn parse_list(value: &serde_yaml::Value, depth: usize) -> Result<Vec<Predicate>, PackParseError> {
let seq = value.as_sequence().ok_or_else(|| PackParseError::InvalidPredicate {
detail: "combiner value must be a sequence of predicate entries".to_string(),
})?;
seq.iter().map(|v| Predicate::from_yaml(v, depth)).collect()
}
fn string_arg(value: &serde_yaml::Value, key: &str) -> Result<String, PackParseError> {
value.as_str().map(str::to_owned).ok_or_else(|| PackParseError::InvalidPredicate {
detail: format!("{key} expects a string argument"),
})
}
fn reg_path(value: &serde_yaml::Value) -> Result<String, PackParseError> {
if value.as_str().is_some() {
return Err(PackParseError::InvalidPredicate {
detail: "reg_key string form is not supported: use { path, name } map".to_string(),
});
}
map_string(value, "reg_key", "path")
}
fn reg_name(value: &serde_yaml::Value) -> Result<Option<String>, PackParseError> {
if value.as_str().is_some() {
return Err(PackParseError::InvalidPredicate {
detail: "reg_key string form is not supported: use { path, name } map".to_string(),
});
}
match value.as_mapping() {
Some(m) => match m.get(serde_yaml::Value::String("name".to_string())) {
Some(v) if v.is_null() => Ok(None),
Some(v) => v.as_str().map(str::to_owned).map(Some).ok_or_else(|| {
PackParseError::InvalidPredicate {
detail: "reg_key.name must be a string".to_string(),
}
}),
None => Ok(None),
},
None => Err(PackParseError::InvalidPredicate {
detail: "reg_key expects a { path, name } map".to_string(),
}),
}
}
fn map_string(
value: &serde_yaml::Value,
outer: &str,
field: &str,
) -> Result<String, PackParseError> {
let map = value.as_mapping().ok_or_else(|| PackParseError::InvalidPredicate {
detail: format!("{outer} expects a mapping argument"),
})?;
let v = map.get(serde_yaml::Value::String(field.to_string())).ok_or_else(|| {
PackParseError::InvalidPredicate {
detail: format!("{outer} missing required field {field:?}"),
}
})?;
v.as_str().map(str::to_owned).ok_or_else(|| PackParseError::InvalidPredicate {
detail: format!("{outer}.{field} must be a string"),
})
}