use std::collections::HashMap; use camino::Utf8PathBuf; use serde::{Deserialize, de}; fn spare_gb_default() -> u64 { 10 } #[derive(Debug, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] pub struct DeviceClass { pub pool: String, #[serde(default = "spare_gb_default")] pub spare_gb: u64, } #[derive(Debug, Clone)] pub struct DeviceClassCollection { pub by_name: HashMap, pub default: Option, } impl DeviceClassCollection { pub fn default(&self) -> Option<&DeviceClass> { self.default.as_ref().map(|d| self.by_name.get(d).unwrap()) } } impl<'de> Deserialize<'de> for DeviceClassCollection { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { #[derive(Debug, Deserialize)] struct DeviceClassRepr { name: String, #[serde(default)] default: bool, #[serde(flatten)] inner: DeviceClass, } let repr = Vec::::deserialize(deserializer)?; let mut defaults_iter = repr.iter().filter(|repr| repr.default); let default = defaults_iter.next().map(|repr| repr.name.clone()); let excess_defaults: Vec<_> = defaults_iter.map(|repr| repr.name.as_str()).collect(); if !excess_defaults.is_empty() { return Err(de::Error::custom(format!( "multiple device classes set as default: {}", excess_defaults.join(", ") ))); } Ok(DeviceClassCollection { by_name: repr .into_iter() .map(|repr| (repr.name, repr.inner)) .collect(), default, }) } } fn socket_name_default() -> Utf8PathBuf { "/run/topolvm/lvmd.sock".into() } #[derive(Debug, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] pub struct Config { pub device_classes: DeviceClassCollection, #[serde(default = "socket_name_default")] pub socket_name: Utf8PathBuf, }