use std::collections::HashMap;
use serde::Deserialize;
use crate::config::{
matching, names, property_key, NameOverride, OverrideType,
};
impl<'de> Deserialize<'de> for NameOverride {
fn deserialize<D: serde::Deserializer<'de>>(
d: D,
) -> Result<Self, D::Error> {
NameOverrideRaw::deserialize(d)?
.try_into()
.map_err(serde::de::Error::custom)
}
}
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
struct NameOverrideRaw {
types: Vec<OverrideType>,
property: Option<property_key::PropertyKey>,
value: Option<String>,
matches: Option<Vec<matching::MatchCondition>>,
templates: Vec<names::NameTemplate>,
}
impl TryFrom<NameOverrideRaw> for NameOverride {
type Error = String;
fn try_from(raw: NameOverrideRaw) -> Result<Self, Self::Error> {
let matches = match (raw.matches, raw.property, raw.value) {
(Some(matches), None, None) => matches,
(None, Some(property), Some(value)) => {
vec![matching::MatchCondition(HashMap::from([(
property,
matching::MatchValue::Literal(value),
)]))]
}
(None, None, None) => {
return Err(
"must specify either `matches` or `property`/`value`"
.into(),
);
}
(Some(_), Some(_), _) | (Some(_), _, Some(_)) => {
return Err(
"cannot specify both `matches` and `property`/`value`"
.into(),
);
}
(None, Some(_), None) => {
return Err("`property` requires `value`".into());
}
(None, None, Some(_)) => {
return Err("`value` requires `property`".into());
}
};
Ok(NameOverride {
types: raw.types,
matches,
templates: raw.templates,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_style() {
let toml = r#"
types = ["stream"]
matches = [{ "node:node.name" = "spotify" }]
templates = ["{node:node.name}"]
"#;
let ovr: NameOverride = toml::from_str(toml).unwrap();
assert_eq!(ovr.matches.len(), 1);
}
#[test]
fn legacy_style() {
let toml = r#"
types = ["stream"]
property = "node:node.name"
value = "spotify"
templates = ["{node:node.name}"]
"#;
let ovr: NameOverride = toml::from_str(toml).unwrap();
assert_eq!(ovr.matches.len(), 1);
}
#[test]
fn both_is_error() {
let toml = r#"
types = ["stream"]
property = "node:node.name"
value = "spotify"
matches = [{ "node:node.name" = "spotify" }]
templates = ["{node:node.name}"]
"#;
assert!(toml::from_str::<NameOverride>(toml).is_err());
}
#[test]
fn neither_is_error() {
let toml = r#"
types = ["stream"]
templates = ["{node:node.name}"]
"#;
assert!(toml::from_str::<NameOverride>(toml).is_err());
}
#[test]
fn property_without_value_is_error() {
let toml = r#"
types = ["stream"]
property = "node:node.name"
templates = ["{node:node.name}"]
"#;
assert!(toml::from_str::<NameOverride>(toml).is_err());
}
#[test]
fn value_without_property_is_error() {
let toml = r#"
types = ["stream"]
value = "spotify"
templates = ["{node:node.name}"]
"#;
assert!(toml::from_str::<NameOverride>(toml).is_err());
}
#[test]
fn legacy_equivalent_to_new() {
let legacy = r#"
types = ["stream"]
property = "node:node.name"
value = "spotify"
templates = ["{node:node.name}"]
"#;
let new = r#"
types = ["stream"]
matches = [{ "node:node.name" = "spotify" }]
templates = ["{node:node.name}"]
"#;
assert_eq!(
toml::from_str::<NameOverride>(legacy).unwrap(),
toml::from_str::<NameOverride>(new).unwrap()
);
}
}