use std::time::Duration;
use serde::Deserialize;
use shep_client::shep_core::values::UpDuration;
use crate::daemon::{Daemon, adopted_name};
use crate::error::Error;
const DEFAULT_INTERVAL: UpDuration = UpDuration::from_millis(30_000);
const DEFAULT_RETENTION: usize = 5;
const MINIMUM_RETENTION: usize = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DogConfig {
pub interval: Duration,
pub retention: usize,
}
#[derive(Deserialize)]
#[serde(default, deny_unknown_fields)]
struct Raw {
interval: UpDuration,
retention: usize,
}
impl Default for Raw {
fn default() -> Self {
Self {
interval: DEFAULT_INTERVAL,
retention: DEFAULT_RETENTION,
}
}
}
impl DogConfig {
pub fn parse(toml: &str) -> Result<Self, Error> {
let raw: Raw = toml::from_str(toml)
.map_err(|source| Error::Config(format!("[dog.<name>]: {source}")))?;
if raw.retention < MINIMUM_RETENTION {
return Err(Error::Config(format!(
"retention = {} keeps too few releases to roll back: the release a failed \
deploy returns to is the second newest, so anything below {MINIMUM_RETENTION} \
prunes the only thing there is to roll back to",
raw.retention
)));
}
let interval = raw.interval.as_duration();
if interval.is_zero() {
return Err(Error::Config(
"interval = \"0\" would fetch continuously rather than on a schedule, which \
reads as a hung dog and hammers the remote it is watching"
.to_owned(),
));
}
Ok(Self {
interval,
retention: raw.retention,
})
}
}
pub async fn read<D: Daemon>(daemon: &D) -> Result<DogConfig, Error> {
let Some(name) = adopted_name(daemon).await else {
return DogConfig::parse("");
};
let section = daemon.dog_config(&name).await?;
DogConfig::parse(§ion)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_empty_section_is_the_documented_defaults() {
let config = DogConfig::parse("").expect("an empty section parses");
assert_eq!(config.interval, Duration::from_secs(30));
assert_eq!(config.retention, 5);
}
#[test]
fn both_keys_are_read() {
let config = DogConfig::parse("interval = \"5m\"\nretention = 12").expect("parses");
assert_eq!(config.interval, Duration::from_secs(300));
assert_eq!(config.retention, 12);
}
#[test]
fn a_retention_below_two_is_refused_by_name() {
for count in ["0", "1"] {
let err = DogConfig::parse(&format!("retention = {count}")).expect_err("refuses");
let shown = err.to_string();
assert!(shown.contains("retention"), "{shown}");
assert!(shown.contains("roll back"), "{shown}");
}
}
#[test]
fn a_zero_interval_is_refused() {
let err = DogConfig::parse("interval = \"0s\"").expect_err("refuses");
assert!(err.to_string().contains("interval"), "{err}");
}
#[test]
fn an_unknown_key_is_refused_and_named() {
let err = DogConfig::parse("retenton = 2").expect_err("refuses");
assert!(err.to_string().contains("retenton"), "{err}");
}
#[test]
fn a_value_of_the_wrong_type_names_the_key() {
let err = DogConfig::parse("retention = \"five\"").expect_err("refuses");
assert!(err.to_string().contains("retention"), "{err}");
}
}