/* * SPDX-FileCopyrightText: 2020 Stalwart Labs Ltd * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ pub mod cron; pub mod ipmask; pub mod parser; pub mod utils; use std::{collections::BTreeMap, time::Duration}; use ahash::AHashMap; use serde::Serialize; #[derive(Debug, Default, Serialize)] pub struct Config { #[serde(skip)] pub keys: BTreeMap, pub warnings: AHashMap, pub errors: AHashMap, #[cfg(debug_assertions)] #[serde(skip)] pub keys_read: parking_lot::Mutex>, } #[derive(Debug, Clone, PartialEq, Eq, Serialize)] #[serde(tag = "type")] pub enum ConfigWarning { Missing, AppliedDefault { default: String }, Unread { value: String }, Build { error: String }, Parse { error: String }, } #[derive(Debug, Clone, PartialEq, Eq, Serialize)] #[serde(tag = "type")] pub enum ConfigError { Parse { error: String }, Build { error: String }, Macro { error: String }, } #[derive(Debug, Default, PartialEq, Eq)] pub struct ConfigKey { pub key: String, pub value: String, } #[derive(Debug, Default, PartialEq, Eq, Clone)] pub struct Rate { pub requests: u64, pub period: Duration, } pub(crate) type Result = std::result::Result; impl Config { pub async fn resolve_macros(&mut self, classes: &[&str]) { for macro_class in classes { self.resolve_macro_type(macro_class).await; } } pub async fn resolve_all_macros(&mut self) { self.resolve_macros(&["env", "file", "cfg"]).await; } async fn resolve_macro_type(&mut self, class: &str) { let macro_start = format!("%{{{class}:"); let mut replacements = AHashMap::new(); 'outer: for (key, value) in &self.keys { if value.contains(¯o_start) && value.contains("}%") { let mut result = String::with_capacity(value.len()); let mut snippet: &str = value.as_str(); loop { if let Some((suffix, macro_name)) = snippet.split_once(¯o_start) { if !suffix.is_empty() { result.push_str(suffix); } if let Some((location, rest)) = macro_name.split_once("}%") { match class { "cfg" => { if let Some(value) = replacements .get(location) .or_else(|| self.keys.get(location)) { result.push_str(value); } else { self.errors.insert( key.clone(), ConfigError::Macro { error: format!("Unknown key {location:?}"), }, ); } } "env" => match std::env::var(location) { Ok(value) => { result.push_str(&value); } Err(_) => { self.errors.insert( key.clone(), ConfigError::Macro { error : format!( "Failed to obtain environment variable {location:?}" )}, ); } }, "file" => { let file_name = location.strip_prefix("//").unwrap_or(location); match tokio::fs::read(file_name).await { Ok(value) => match String::from_utf8(value) { Ok(value) => { result.push_str(&value); } Err(err) => { self.errors.insert( key.clone(), ConfigError::Macro { error: format!( "Failed to read file {file_name:?}: {err}" ), }, ); continue 'outer; } }, Err(err) => { self.errors.insert( key.clone(), ConfigError::Macro { error: format!( "Failed to read file {file_name:?}: {err}" ), }, ); continue 'outer; } } } _ => { unreachable!() } }; snippet = rest; } } else { result.push_str(snippet); break; } } replacements.insert(key.clone(), result); } } if !replacements.is_empty() { for (key, value) in replacements { self.keys.insert(key, value); } } } pub fn update(&mut self, settings: Vec<(String, String)>) { self.keys.extend(settings); } pub fn log_errors(&self, use_stderr: bool) { for (key, err) in &self.errors { let (cause, message) = match err { ConfigError::Parse { error } => ( trc::ConfigEvent::ParseError, format!("Failed to parse setting {key:?}: {error}"), ), ConfigError::Build { error } => ( trc::ConfigEvent::BuildError, format!("Build error for key {key:?}: {error}"), ), ConfigError::Macro { error } => ( trc::ConfigEvent::MacroError, format!("Macro expansion error for setting {key:?}: {error}"), ), }; if !use_stderr { trc::event!(Config(cause), Details = message); } else { eprintln!("ERROR: {message}"); } } } pub fn log_warnings(&mut self, use_stderr: bool) { #[cfg(debug_assertions)] self.warn_unread_keys(); for (key, warn) in &self.warnings { let (cause, message) = match warn { ConfigWarning::AppliedDefault { default } => ( trc::ConfigEvent::DefaultApplied, format!("WARNING: Missing setting {key:?}, applied default {default:?}"), ), ConfigWarning::Missing => ( trc::ConfigEvent::MissingSetting, format!("WARNING: Missing setting {key:?}"), ), ConfigWarning::Unread { value } => ( trc::ConfigEvent::UnusedSetting, format!("WARNING: Unused setting {key:?} with value {value:?}"), ), ConfigWarning::Parse { error } => ( trc::ConfigEvent::ParseWarning, format!("WARNING: Failed to parse {key:?}: {error}"), ), ConfigWarning::Build { error } => ( trc::ConfigEvent::BuildWarning, format!("WARNING for {key:?}: {error}"), ), }; if !use_stderr { trc::event!(Config(cause), Details = message); } else { eprintln!("{}", message); } } } } impl Clone for Config { fn clone(&self) -> Self { Self { keys: self.keys.clone(), warnings: self.warnings.clone(), errors: self.errors.clone(), #[cfg(debug_assertions)] keys_read: Default::default(), } } } impl PartialEq for Config { fn eq(&self, other: &Self) -> bool { self.keys == other.keys && self.warnings == other.warnings && self.errors == other.errors } } impl Eq for Config {} impl From<(String, String)> for ConfigKey { fn from((key, value): (String, String)) -> Self { Self { key, value } } } impl From<(&str, &str)> for ConfigKey { fn from((key, value): (&str, &str)) -> Self { Self { key: key.to_string(), value: value.to_string(), } } } impl From<(&str, String)> for ConfigKey { fn from((key, value): (&str, String)) -> Self { Self { key: key.to_string(), value, } } } impl From<(String, &str)> for ConfigKey { fn from((key, value): (String, &str)) -> Self { Self { key, value: value.to_string(), } } }