use crate::config::ConfigError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CrossLinkValue {
Wildcard,
List(Vec<String>),
}
impl CrossLinkValue {
pub fn parse_toml(location: &str, value: &toml::Value) -> Result<Self, ConfigError> {
match value {
toml::Value::String(s) if s == "*" => Ok(Self::Wildcard),
toml::Value::String(other) => Err(ConfigError::Other(format!(
"{location}: expected `\"*\"` or a list of mem names, got string {other:?}"
))),
toml::Value::Array(items) => {
let mut names: Vec<String> = Vec::with_capacity(items.len());
let mut has_wildcard = false;
for (idx, item) in items.iter().enumerate() {
match item {
toml::Value::String(s) if s == "*" => {
has_wildcard = true;
}
toml::Value::String(s) if s.is_empty() => {
return Err(ConfigError::Other(format!(
"{location}[{idx}]: mem name must not be empty"
)));
}
toml::Value::String(s) => names.push(s.clone()),
_ => {
return Err(ConfigError::Other(format!(
"{location}[{idx}]: expected string mem name, got {item}"
)));
}
}
}
if has_wildcard && !names.is_empty() {
return Err(ConfigError::Other(format!(
"{location}: `\"*\"` wildcard must be the sole entry — \
remove the named entries or drop the wildcard"
)));
}
if has_wildcard {
Ok(Self::Wildcard)
} else {
Ok(Self::List(names))
}
}
other => Err(ConfigError::Other(format!(
"{location}: expected `\"*\"` or a list of mem names, got {other}"
))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn val(s: &str) -> toml::Value {
toml::from_str::<toml::Value>(&format!("x = {s}"))
.unwrap()
.get("x")
.unwrap()
.clone()
}
#[test]
fn wildcard_string_parses() {
assert_eq!(
CrossLinkValue::parse_toml("[loc]", &val("\"*\"")).unwrap(),
CrossLinkValue::Wildcard
);
}
#[test]
fn name_list_parses() {
assert_eq!(
CrossLinkValue::parse_toml("[loc]", &val("[\"a\", \"b\"]")).unwrap(),
CrossLinkValue::List(vec!["a".to_string(), "b".to_string()])
);
}
#[test]
fn empty_list_is_default_deny() {
assert_eq!(
CrossLinkValue::parse_toml("[loc]", &val("[]")).unwrap(),
CrossLinkValue::List(vec![])
);
}
#[test]
fn wildcard_in_a_list_is_a_lone_wildcard() {
assert_eq!(
CrossLinkValue::parse_toml("[loc]", &val("[\"*\"]")).unwrap(),
CrossLinkValue::Wildcard
);
}
#[test]
fn mixed_wildcard_and_names_is_rejected() {
let err = CrossLinkValue::parse_toml("[loc]", &val("[\"*\", \"a\"]")).unwrap_err();
assert!(format!("{err}").contains("sole entry"));
}
#[test]
fn empty_mem_name_is_rejected() {
let err = CrossLinkValue::parse_toml("[loc]", &val("[\"\"]")).unwrap_err();
assert!(format!("{err}").contains("must not be empty"));
}
#[test]
fn non_string_list_entry_is_rejected() {
let err = CrossLinkValue::parse_toml("[loc]", &val("[1]")).unwrap_err();
assert!(format!("{err}").contains("expected string mem name"));
}
#[test]
fn bare_integer_is_rejected() {
let err = CrossLinkValue::parse_toml("[loc]", &val("42")).unwrap_err();
assert!(format!("{err}").contains("expected"));
}
}