use std::collections::HashMap;
pub(crate) fn parse_comma_list(s: &str) -> Vec<String> {
s.split(',')
.map(|item| item.trim())
.filter(|item| !item.is_empty())
.map(|item| item.to_string())
.collect()
}
#[cfg(feature = "file")]
mod serde_impl {
use super::ConfigValue;
use serde::de::{self, Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
use std::fmt;
struct ConfigValueVisitor;
impl<'de> Visitor<'de> for ConfigValueVisitor {
type Value = ConfigValue;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("any valid configuration value")
}
fn visit_bool<E: de::Error>(self, v: bool) -> Result<ConfigValue, E> {
Ok(ConfigValue::Scalar(v.to_string()))
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<ConfigValue, E> {
Ok(ConfigValue::Scalar(v.to_string()))
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<ConfigValue, E> {
Ok(ConfigValue::Scalar(v.to_string()))
}
fn visit_f64<E: de::Error>(self, v: f64) -> Result<ConfigValue, E> {
Ok(ConfigValue::Scalar(v.to_string()))
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<ConfigValue, E> {
Ok(ConfigValue::Scalar(v.to_owned()))
}
fn visit_string<E: de::Error>(self, v: String) -> Result<ConfigValue, E> {
Ok(ConfigValue::Scalar(v))
}
fn visit_none<E: de::Error>(self) -> Result<ConfigValue, E> {
Ok(ConfigValue::Scalar(String::new()))
}
fn visit_unit<E: de::Error>(self) -> Result<ConfigValue, E> {
Ok(ConfigValue::Scalar(String::new()))
}
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<ConfigValue, A::Error> {
let mut items = Vec::new();
while let Some(val) = seq.next_element::<ConfigValue>()? {
match val {
ConfigValue::Scalar(s) => items.push(s),
_ => return Err(de::Error::custom(
"nested values inside sequences are not supported; list items must be scalars",
)),
}
}
Ok(ConfigValue::List(items))
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<ConfigValue, A::Error> {
let mut result = super::ValueMap::new();
while let Some((key, value)) = map.next_entry::<String, ConfigValue>()? {
result.insert(key, value);
}
Ok(ConfigValue::Nested(result))
}
}
impl<'de> Deserialize<'de> for ConfigValue {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_any(ConfigValueVisitor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_expecting() {
use std::fmt::Write;
let mut buf = String::new();
let visitor = ConfigValueVisitor;
write!(buf, "{}", DisplayExpecting(visitor)).unwrap();
assert_eq!(buf, "any valid configuration value");
}
struct DisplayExpecting<V>(V);
impl<V: Visitor<'static>> fmt::Display for DisplayExpecting<V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.expecting(f)
}
}
#[test]
fn test_visit_string() {
let visitor = ConfigValueVisitor;
let result: Result<ConfigValue, serde::de::value::Error> =
visitor.visit_string("owned".to_owned());
match result.unwrap() {
ConfigValue::Scalar(s) => assert_eq!(s, "owned"),
other => panic!("expected Scalar, got {other:?}"),
}
}
#[test]
fn test_visit_none() {
let visitor = ConfigValueVisitor;
let result: Result<ConfigValue, serde::de::value::Error> = visitor.visit_none();
match result.unwrap() {
ConfigValue::Scalar(s) => assert!(s.is_empty()),
other => panic!("expected empty Scalar, got {other:?}"),
}
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum ConfigValue {
Scalar(String),
Nested(ValueMap),
List(Vec<String>),
}
pub type ValueMap = HashMap<String, ConfigValue>;
pub fn merge_value_maps(target: &mut ValueMap, source: &ValueMap) {
for (key, value) in source {
match value {
ConfigValue::Nested(source_nested) => {
match target.get_mut(key) {
Some(ConfigValue::Nested(target_nested)) => {
merge_value_maps(target_nested, source_nested);
}
_ => {
target.insert(key.clone(), ConfigValue::Nested(source_nested.clone()));
}
}
}
_ => {
target.insert(key.clone(), value.clone());
}
}
}
}