use crate::values::UpDuration;
use serde::Deserialize;
use std::collections::BTreeMap;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct DaemonSection {
pub log_json: bool,
pub log_level: LogLevel,
pub environment: String,
pub socket: Option<PathBuf>,
pub enabled_dogs: Vec<String>,
pub adopted_dogs: BTreeMap<String, PathBuf>,
pub boot_first_dogs: Vec<String>,
pub max_cron_sleep: Option<UpDuration>,
}
impl Default for DaemonSection {
fn default() -> Self {
Self {
log_json: false,
log_level: LogLevel::default(),
environment: "production".to_string(),
socket: None,
enabled_dogs: Vec::new(),
adopted_dogs: BTreeMap::new(),
boot_first_dogs: Vec::new(),
max_cron_sleep: None,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum LogLevel {
Off,
Error,
#[default]
Warn,
Info,
Debug,
Trace,
}
pub(super) const MIN_CRON_SLEEP: UpDuration = UpDuration::from_millis(1_000);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct WhistleSection {
pub allow_control: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct SecretsSection {
pub allow_read: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
#[serde(deny_unknown_fields, default)]
pub struct StyleSection {
pub level: Option<String>,
}
impl LogLevel {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Off => "off",
Self::Error => "error",
Self::Warn => "warn",
Self::Info => "info",
Self::Debug => "debug",
Self::Trace => "trace",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
match name {
"off" => Some(Self::Off),
"error" => Some(Self::Error),
"warn" => Some(Self::Warn),
"info" => Some(Self::Info),
"debug" => Some(Self::Debug),
"trace" => Some(Self::Trace),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::super::config::DaemonConfig;
use super::super::testing::*;
use super::*;
#[test]
fn an_unset_log_level_is_warn() {
assert_eq!(
DaemonConfig::load(None, &no_env).unwrap().daemon.log_level,
LogLevel::Warn
);
}
#[test]
fn every_log_level_name_means_the_same_thing_in_the_file_and_the_environment() {
let levels = [
LogLevel::Off,
LogLevel::Error,
LogLevel::Warn,
LogLevel::Info,
LogLevel::Debug,
LogLevel::Trace,
];
for level in levels {
let name = level.as_str();
assert_eq!(LogLevel::from_name(name), Some(level), "from_name({name})");
let file = format!("[daemon]\nlog_level = \"{name}\"");
let cfg = DaemonConfig::load(Some(&file), &no_env).unwrap();
assert_eq!(cfg.daemon.log_level, level, "[daemon] log_level = {name:?}");
let env = |k: &str| (k == "SHEP_LOG_LEVEL").then(|| name.to_string());
let cfg = DaemonConfig::load(None, &env).unwrap();
assert_eq!(cfg.daemon.log_level, level, "SHEP_LOG_LEVEL={name}");
}
}
#[test]
fn debug_redacts_dog_values() {
let cfg = DaemonConfig::load(Some("[dog.metrics]\nport = 9615"), &no_env).unwrap();
assert_eq!(
format!("{cfg:?}"),
"DaemonConfig { daemon: DaemonSection { log_json: false, log_level: Warn, environment: \"production\", socket: None, enabled_dogs: [], adopted_dogs: {}, boot_first_dogs: [], max_cron_sleep: None }, whistle: WhistleSection { allow_control: false }, secrets: SecretsSection { allow_read: false }, style: StyleSection { level: None }, interpreters: {}, dog: <1 tables> }"
);
}
}