use core::fmt;
use std::collections::BTreeMap;
#[derive(Clone, Default, PartialEq)]
pub struct DogsConfig {
pub dog: BTreeMap<String, toml::Table>,
}
impl fmt::Debug for DogsConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DogsConfig")
.field("dog", &format_args!("<{} tables>", self.dog.len()))
.finish()
}
}
impl DogsConfig {
pub fn load(source: Option<&str>) -> Result<Self, DogsConfigError> {
let Some(source) = source else {
return Ok(Self::default());
};
let dog = toml::from_str(source).map_err(DogsConfigError::Toml)?;
Ok(Self { dog })
}
}
#[non_exhaustive]
pub enum DogsConfigError {
Toml(toml::de::Error),
}
impl fmt::Debug for DogsConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Toml(err) => f.debug_tuple("Toml").field(&err.message()).finish(),
}
}
}
impl fmt::Display for DogsConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Toml(err) => write!(f, "invalid TOML in dogs.toml: {err}"),
}
}
}
impl core::error::Error for DogsConfigError {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Toml(err) => Some(err),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_missing_file_loads_as_an_empty_map() {
let config = DogsConfig::load(None).expect("None is not an error");
assert!(config.dog.is_empty());
}
#[test]
fn sections_are_keyed_by_name_with_no_prefix() {
let source = "[metrics]\nbind = \"127.0.0.1:9615\"\n\n[bark.sinks]\noncall = { kind = \"discord\" }\n";
let config = DogsConfig::load(Some(source)).expect("valid TOML");
assert_eq!(
config.dog.keys().collect::<Vec<_>>(),
vec!["bark", "metrics"]
);
assert_eq!(
config.dog["metrics"]["bind"].as_str(),
Some("127.0.0.1:9615")
);
}
#[test]
fn invalid_toml_is_a_named_error() {
let err = DogsConfig::load(Some("[metrics")).expect_err("unterminated table header");
assert!(matches!(err, DogsConfigError::Toml(_)));
}
#[test]
fn debug_redacts_the_source_a_parse_error_carries() {
let source =
"[bark.sinks]\noncall = { url = \"https://discord.com/api/webhooks/SECRET\" }\n[oops\n";
let err = DogsConfig::load(Some(source)).expect_err("unterminated table header");
assert_eq!(
format!("{err:?}"),
"Toml(\"invalid table header\\nexpected `.`, `]`\")"
);
assert!(
err.to_string().contains("line 3, column 6"),
"Display keeps its line-and-column context: {err}"
);
assert!(
!err.to_string().contains("SECRET"),
"and it quotes only the line that failed: {err}"
);
}
#[test]
fn debug_redacts_every_dog_section() {
let source =
"[bark.sinks]\noncall = { url = \"https://discord.com/api/webhooks/SECRET\" }\n";
let config = DogsConfig::load(Some(source)).expect("valid TOML");
assert_eq!(format!("{config:?}"), "DogsConfig { dog: <1 tables> }");
}
}