mod name; mod source; mod target; pub use crate::config::name::{DeviceName, NamespaceName}; pub use crate::config::source::ForwardSource; pub use crate::config::target::ForwardTarget; use serde::Deserialize; use std::collections::HashSet; use std::fs::read_to_string; use std::path::{Path, PathBuf}; use thiserror::Error; use toml::from_str; #[derive(Debug)] pub struct Config { path: PathBuf, pub namespaces: Vec, } impl Config { pub fn load>(path: P) -> Result { let path = path.as_ref(); let raw = read_to_string(path).map_err(|error| ConfigError::Read { error, path: path.to_owned(), })?; let config: RawConfig = from_str(&raw).map_err(|error| ConfigError::Parse { error, path: path.to_owned(), })?; config .validate(path) .map_err(|error| ConfigError::Validation { error, path: path.to_owned(), }) } pub fn reload(&self) -> Result { Self::load(&self.path) } pub fn get_namespace<'a>(&'a self, name: &NamespaceName) -> Option<&'a NamespaceConfig> { self.namespaces .iter() .find(|namespace| &namespace.name == name) } } #[derive(Deserialize, Debug)] #[serde(deny_unknown_fields)] struct RawConfig { #[serde(default, rename = "namespace")] pub namespaces: Vec, } impl RawConfig { fn validate(self, path: &Path) -> Result { let mut sources = HashSet::new(); for source in self .namespaces .iter() .flat_map(|namespace| namespace.forward.iter()) .map(|forward| &forward.source) { if !sources.insert(source.clone()) { return Err(ValidationError::DuplicateSource { forward_source: source.clone(), }); } } Ok(Config { path: path.into(), namespaces: self.namespaces, }) } } #[derive(Deserialize, Debug)] pub struct NamespaceConfig { pub name: NamespaceName, #[serde(default)] pub forward: Vec, #[serde(default)] pub devices: Vec, } #[derive(Deserialize, Debug)] pub struct ForwardConfig { pub source: ForwardSource, pub target: ForwardTarget, #[serde(default)] pub reverse: bool, } #[derive(Debug, Error)] pub enum ConfigError { #[error("Error while reading config from {}: {error:#}", path.display())] Read { error: std::io::Error, path: PathBuf, }, #[error("Error while parsing config from {}: {error:#}", path.display())] Parse { error: toml::de::Error, path: PathBuf, }, #[error("Error while validating config from {}: {error:#}", path.display())] Validation { error: ValidationError, path: PathBuf, }, } #[derive(Debug, Error)] pub enum ValidationError { #[error("Duplicate source in forwards: {forward_source}")] DuplicateSource { forward_source: ForwardSource }, }