use serde::{Deserialize, Deserializer};
use std::collections::HashMap;
use std::fmt::Display;
use std::str::FromStr;
pub trait TryFromKv: Sized {
type Err: Display;
fn try_from_kv(key: String, val: String) -> Result<Self, Self::Err>;
}
pub fn polymorphic_vec<'de, D, T, C>(deserializer: D) -> Result<C, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + FromStr + TryFromKv,
C: From<Vec<T>>,
<T as FromStr>::Err: Display,
<T as TryFromKv>::Err: Display,
{
fn resolve_list<T, E>(items: Vec<Item<T>>) -> Result<Vec<T>, E>
where
T: FromStr,
<T as FromStr>::Err: Display,
E: serde::de::Error,
{
items
.into_iter()
.map(|item| match item {
Item::Obj(val) => Ok(val),
Item::Str(s) => s.parse().map_err(serde::de::Error::custom),
})
.collect()
}
#[derive(Deserialize)]
#[serde(untagged)]
enum Item<T> {
Str(String),
Obj(T),
}
#[derive(Deserialize)]
#[serde(untagged)]
enum Container<T> {
List(Vec<Item<T>>),
Map(HashMap<String, String>),
Single(Item<T>),
}
let vec = match Container::<T>::deserialize(deserializer)? {
Container::List(items) => resolve_list(items)?,
Container::Map(map) => map
.into_iter()
.map(|(k, v)| T::try_from_kv(k, v).map_err(serde::de::Error::custom))
.collect::<Result<Vec<T>, _>>()?,
Container::Single(item) => resolve_list(vec![item])?,
};
Ok(C::from(vec))
}
pub fn vec_replace<T>(base: Vec<T>, top: Vec<T>) -> Vec<T> {
if top.is_empty() { base } else { top }
}
pub fn vec_extend<T>(mut base: Vec<T>, top: Vec<T>) -> Vec<T> {
base.extend(top);
base
}
pub fn vec_dedup<T: PartialEq>(mut base: Vec<T>, top: Vec<T>) -> Vec<T> {
for item in top {
if !base.contains(&item) {
base.push(item);
}
}
base
}
#[cfg(test)]
mod tests {
use super::*;
use crate::path::PathMapping;
use serde::Deserialize;
#[derive(Deserialize)]
struct Config {
#[serde(deserialize_with = "polymorphic_vec")]
map: Vec<PathMapping>,
}
#[test]
fn test_vec_replace() {
let base = vec!["a", "b"];
let top = vec!["c"];
let empty: Vec<&str> = vec![];
assert_eq!(vec_replace(base.clone(), top.clone()), vec!["c"]);
assert_eq!(vec_replace(base.clone(), empty), vec!["a", "b"]);
}
#[test]
fn test_vec_dedup() {
let base = vec![1, 2, 3];
let top = vec![3, 4, 5];
let merged = vec_dedup(base, top);
assert_eq!(merged, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_vec_dedup_base_duplicates() {
let base = vec![1, 1, 2];
let top = vec![2, 3];
let merged = vec_dedup(base, top);
assert_eq!(merged, vec![1, 1, 2, 3]);
}
#[test]
fn test_path_mapping_polymorphism() {
let source_file = tempfile::NamedTempFile::new().unwrap();
let src_path = source_file.path().to_str().unwrap();
let toml_input = format!(
r#"
map = [
"{src}:/tmp/dst1",
{{ src = "{src}", dst = "/tmp/dst2" }}
]
"#,
src = src_path
);
let config: Config = toml::from_str(&toml_input).expect("Parsing failed");
assert_eq!(config.map.len(), 2);
assert_eq!(config.map[0].src().as_path(), source_file.path());
assert_eq!(config.map[1].src().as_path(), source_file.path());
}
}