pub mod loader;
pub mod reload;
pub mod types;
pub mod validation;
use crate::Result;
use serde::{Deserialize, Serialize};
use std::path::Path;
pub use reload::ConfigReloader;
pub use types::PluginConfig;
#[cfg(feature = "log")]
pub use lazylog::{
FileLogConfig as LazylogFileConfig, LogConfig as LazylogLogConfig, RotationPeriod,
RotationTrigger,
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FileLogConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_file_path")]
pub path: String,
#[serde(default)]
#[cfg(feature = "log")]
pub rotation: RotationTrigger,
#[serde(default)]
pub compress: bool,
}
fn default_file_path() -> String {
"lazydns.log".to_string()
}
impl Default for FileLogConfig {
fn default() -> Self {
Self {
enabled: false,
path: default_file_path(),
#[cfg(feature = "log")]
rotation: RotationTrigger::default(),
compress: false,
}
}
}
impl FileLogConfig {
pub fn new<S: Into<String>>(path: S) -> Self {
Self {
path: path.into(),
enabled: false,
#[cfg(feature = "log")]
rotation: RotationTrigger::default(),
compress: false,
}
}
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
}
#[cfg(feature = "log")]
impl FileLogConfig {
pub fn with_rotation(mut self, rotation: RotationTrigger) -> Self {
self.rotation = rotation;
self
}
pub fn to_lazylog(&self) -> LazylogFileConfig {
LazylogFileConfig::new(self.path.clone()).with_rotation_trigger(self.rotation.clone())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct LogConfig {
#[serde(default = "default_log_level")]
pub level: String,
#[serde(default = "default_console")]
pub console: bool,
#[serde(default = "default_log_format")]
pub format: String,
#[serde(default)]
pub file: Option<FileLogConfig>,
}
fn default_log_level() -> String {
"info".to_string()
}
fn default_console() -> bool {
false
}
fn default_log_format() -> String {
"text".to_string()
}
impl Default for LogConfig {
fn default() -> Self {
Self {
level: default_log_level(),
console: default_console(),
format: default_log_format(),
file: None,
}
}
}
impl LogConfig {
pub fn new() -> Self {
Self::default()
}
pub fn is_file_logging_enabled(&self) -> bool {
self.file.as_ref().is_some_and(|f| f.enabled)
}
pub fn is_console_logging_enabled(&self) -> bool {
self.console
}
pub fn summary(&self) -> String {
format!(
"LogConfig {{ level: {}, console: {}, format: {}, file_logging: {} }}",
self.level,
self.console,
self.format,
self.is_file_logging_enabled()
)
}
}
#[cfg(feature = "log")]
impl LogConfig {
pub fn to_lazylog(&self, log_spec: String) -> LazylogLogConfig {
let mut config = LazylogLogConfig::new()
.with_console(self.console)
.with_level(log_spec)
.with_format(self.format.clone());
if let Some(ref file) = self.file
&& file.enabled
{
config = config.with_file(file.to_lazylog());
}
config
}
pub fn to_lazylog_with_default(&self) -> LazylogLogConfig {
self.to_lazylog(self.level.clone())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdminConfig {
#[serde(default = "default_admin_enabled")]
pub enabled: bool,
#[serde(default = "default_admin_addr")]
pub addr: String,
}
fn default_admin_enabled() -> bool {
false
}
fn default_admin_addr() -> String {
"127.0.0.1:8080".to_string()
}
impl Default for AdminConfig {
fn default() -> Self {
Self {
enabled: default_admin_enabled(),
addr: default_admin_addr(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MonitoringConfig {
#[serde(default = "default_monitoring_enabled")]
pub enabled: bool,
#[serde(default = "default_monitoring_addr")]
pub addr: String,
}
fn default_monitoring_enabled() -> bool {
false
}
fn default_monitoring_addr() -> String {
"127.0.0.1:9090".to_string()
}
impl Default for MonitoringConfig {
fn default() -> Self {
Self {
enabled: default_monitoring_enabled(),
addr: default_monitoring_addr(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(default)]
pub plugins: Vec<PluginConfig>,
#[serde(default = "default_log_config")]
pub log: LogConfig,
#[serde(default = "default_admin_config")]
pub admin: AdminConfig,
#[serde(default = "default_monitoring_config", alias = "metrics")]
pub monitoring: MonitoringConfig,
}
fn default_log_config() -> LogConfig {
LogConfig::default()
}
fn default_admin_config() -> AdminConfig {
AdminConfig::default()
}
fn default_monitoring_config() -> MonitoringConfig {
MonitoringConfig::default()
}
impl Default for Config {
fn default() -> Self {
Self {
plugins: Vec::new(),
log: default_log_config(),
admin: default_admin_config(),
monitoring: default_monitoring_config(),
}
}
}
impl Config {
pub fn new() -> Self {
Self::default()
}
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
loader::load_from_file(path)
}
pub fn from_yaml(yaml: &str) -> Result<Self> {
loader::load_from_yaml(yaml)
}
pub fn validate(&self) -> Result<()> {
validation::validate_config(self)
}
pub fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
loader::save_to_file(self, path)
}
pub fn to_yaml(&self) -> Result<String> {
loader::to_yaml(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config() {
let config = Config::default();
assert_eq!(config.log.level, "info");
assert!(config.plugins.is_empty());
}
#[test]
fn test_new_config() {
let config = Config::new();
assert_eq!(config.log.level, "info");
}
#[test]
fn test_from_yaml_minimal() {
let yaml = r#"
log:
level: debug
"#;
let config = Config::from_yaml(yaml).unwrap();
assert_eq!(config.log.level, "debug");
}
#[test]
fn test_to_yaml() {
let config = Config::new();
let yaml = config.to_yaml().unwrap();
assert!(yaml.contains("log:"));
}
#[test]
fn test_log_config_new() {
let config = LogConfig::new();
assert_eq!(config.level, "info");
assert!(!config.console);
assert_eq!(config.format, "text");
assert!(config.file.is_none());
}
#[test]
fn test_log_config_is_console_logging_enabled() {
let mut config = LogConfig::default();
assert!(!config.is_console_logging_enabled());
config.console = true;
assert!(config.is_console_logging_enabled());
}
#[test]
fn test_log_config_is_file_logging_enabled() {
let mut config = LogConfig::default();
assert!(!config.is_file_logging_enabled());
config.file = Some(FileLogConfig::new("test.log"));
assert!(!config.is_file_logging_enabled());
config.file = Some(FileLogConfig::new("test.log").with_enabled(true));
assert!(config.is_file_logging_enabled());
}
#[test]
fn test_log_config_summary() {
let config = LogConfig::default();
let summary = config.summary();
assert!(summary.contains("level: info"));
assert!(summary.contains("console: false"));
assert!(summary.contains("file_logging: false"));
}
#[test]
#[cfg(feature = "log")]
fn test_log_config_to_lazylog_with_level_override() {
let config = LogConfig {
level: "debug".to_string(),
console: true,
format: "json".to_string(),
file: None,
};
let lazy = config.to_lazylog("info".to_string());
assert_eq!(lazy.level, "info"); assert!(lazy.console);
assert_eq!(lazy.format, "json");
assert!(lazy.file.is_none());
}
#[test]
#[cfg(feature = "log")]
fn test_log_config_to_lazylog_with_default() {
let config = LogConfig {
level: "debug".to_string(),
console: true,
format: "text".to_string(),
file: None,
};
let lazy = config.to_lazylog_with_default();
assert_eq!(lazy.level, "debug"); assert!(lazy.console);
assert_eq!(lazy.format, "text");
assert!(lazy.file.is_none());
}
#[test]
#[cfg(feature = "log")]
fn test_log_config_to_lazylog_file_enabled() {
let config = LogConfig {
console: true,
file: Some(FileLogConfig::new("test.log").with_enabled(true)),
..Default::default()
};
let lazy = config.to_lazylog("info".to_string());
assert!(lazy.console);
assert!(lazy.file.is_some());
assert_eq!(lazy.file.unwrap().path.to_string_lossy(), "test.log");
}
#[test]
#[cfg(feature = "log")]
fn test_log_config_to_lazylog_file_disabled() {
let config = LogConfig {
console: true,
file: Some(FileLogConfig::new("test.log").with_enabled(false)),
..Default::default()
};
let lazy = config.to_lazylog("info".to_string());
assert!(lazy.console);
assert!(lazy.file.is_none()); }
#[test]
fn test_file_log_config_new() {
let config = FileLogConfig::new("app.log");
assert_eq!(config.path, "app.log");
assert!(!config.enabled);
assert!(!config.compress);
}
#[test]
fn test_file_log_config_with_enabled() {
let config = FileLogConfig::new("app.log").with_enabled(true);
assert!(config.enabled);
}
#[test]
#[cfg(feature = "log")]
fn test_file_log_config_with_rotation() {
let config = FileLogConfig::new("app.log")
.with_enabled(true)
.with_rotation(RotationTrigger::Never);
assert!(config.enabled);
assert_eq!(config.rotation, RotationTrigger::Never);
}
#[test]
#[cfg(feature = "log")]
fn test_file_log_config_to_lazylog() {
let config = FileLogConfig::new("test.log")
.with_enabled(true)
.with_rotation(RotationTrigger::Never);
let lazy = config.to_lazylog();
assert_eq!(lazy.path.to_string_lossy(), "test.log");
}
}