use super::rules::{Rule, Rules};
use super::sinks::Sink;
use serde::Deserialize;
use shep_client::dogs::DogConfig;
use shep_core::barks::{self};
use shep_core::values::UpDuration;
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Deserialize, schemars::JsonSchema, DogConfig)]
#[serde(deny_unknown_fields, default)]
pub struct BarkConfig {
#[shep(secret)]
pub sinks: BTreeMap<String, Sink>,
pub rules: Vec<Rule>,
pub poll: UpDuration,
pub history_bytes: u64,
pub sink_timeout: UpDuration,
}
pub fn rules_for(config: &BarkConfig) -> Result<Rules, super::rules::RulesError> {
let rule_list = if config.rules.is_empty() {
Rules::default_rules(&config.sinks)
} else {
config.rules.clone()
};
Rules::new(rule_list, &config.sinks)
}
impl Default for BarkConfig {
fn default() -> Self {
Self {
sinks: BTreeMap::new(),
rules: Vec::new(),
poll: UpDuration::from_millis(30_000),
history_bytes: barks::DEFAULT_MAX_BYTES,
sink_timeout: UpDuration::from_millis(10_000),
}
}
}
#[cfg(test)]
mod tests {
use shep_core::barks::{self};
use shep_core::values::UpDuration;
use super::super::rules::Rules;
use super::*;
#[test]
fn the_bark_schema_marks_the_sinks_map_and_leaves_the_rules_plain() {
let schema = shep_client::dogs::config_schema::<BarkConfig>()
.expect("`sinks` is a property of this type");
let schema = schema.as_value();
assert_eq!(
schema.pointer("/properties/sinks/x-shep-secret"),
Some(&serde_json::Value::Bool(true)),
"every sink holds a webhook URL, which is a bearer credential"
);
assert_eq!(
schema.pointer("/properties/rules/x-shep-secret"),
None,
"a rule names sinks and holds no credential of its own"
);
assert!(
schema.pointer("/$defs/Rule/properties/sinks").is_some(),
"the pointer below is only a check while `Rule` still has this \
shape, so a rename that moved it must fail here first"
);
assert_eq!(
schema.pointer("/$defs/Rule/properties/sinks/x-shep-secret"),
None,
"a rule's sinks are names, and a name is not a credential"
);
}
#[test]
fn an_empty_section_gets_sane_defaults_not_zeros() {
let parsed: BarkConfig = toml::from_str("").unwrap();
assert_eq!(parsed, BarkConfig::default());
assert_eq!(BarkConfig::default().poll.as_millis(), 30_000);
assert_eq!(
BarkConfig::default().history_bytes,
barks::DEFAULT_MAX_BYTES
);
assert_eq!(BarkConfig::default().sink_timeout.as_millis(), 10_000);
}
#[test]
fn the_documented_bark_config_parses_from_toml() {
let toml_str = r#"
[sinks]
oncall = { kind = "discord", url = "https://discord.com/api/webhooks/..." }
audit = { kind = "json", url = "https://example.internal/hook" }
[[rules]]
on = "gave_up"
sinks = ["oncall", "audit"]
[[rules]]
on = "restart_rate"
restarts = 5
within = "2m"
sinks = ["oncall"]
"#;
let config: BarkConfig =
toml::from_str(toml_str).expect("the documented [dog.bark] example must parse");
assert_eq!(config.sinks.len(), 2);
assert_eq!(config.rules.len(), 2);
assert_eq!(
config.rules[0].when,
super::super::rules::Trigger::GaveUp {}
);
assert_eq!(config.rules[0].sinks, vec!["oncall", "audit"]);
assert_eq!(
config.rules[1].when,
super::super::rules::Trigger::RestartRate {
restarts: 5,
within: UpDuration::from_millis(2 * 60_000),
}
);
assert_eq!(config.rules[1].sinks, vec!["oncall"]);
Rules::new(config.rules, &config.sinks).expect("both documented rules route to real sinks");
}
}