pub(crate) const KNOWN_TOP_LEVEL_KEYS: &[&str] = &[
"domain",
"postmaster",
"smtp",
"imap",
"jmap",
"pop3",
"storage",
"processors",
"runtime_dir",
"relay",
"auth",
"logging",
"queue",
"security",
"domains",
"metrics",
"tracing",
"connection_limits",
"performance",
"tls",
"chroot",
"run_as_user",
"run_as_group",
];
pub(crate) const KNOWN_SECTION_KEYS: &[(&str, &[&str])] = &[
(
"smtp",
&[
"host",
"port",
"tls_port",
"max_message_size",
"require_auth",
"enable_starttls",
"rate_limit",
],
),
("imap", &["host", "port", "tls_port"]),
("jmap", &["host", "port", "base_url"]),
(
"pop3",
&["host", "port", "tls_port", "timeout_seconds", "enable_stls"],
),
(
"relay",
&["host", "port", "username", "password", "use_tls"],
),
("logging", &["level", "format", "output", "file"]),
(
"queue",
&[
"initial_delay",
"max_delay",
"backoff_multiplier",
"max_attempts",
"worker_threads",
"batch_size",
],
),
(
"security",
&[
"relay_networks",
"blocked_ips",
"check_recipient_exists",
"reject_unknown_recipients",
],
),
("domains", &["local_domains", "aliases"]),
(
"metrics",
&["enabled", "bind_address", "path", "basic_auth"],
),
(
"tracing",
&[
"enabled",
"endpoint",
"protocol",
"service_name",
"sample_ratio",
],
),
(
"connection_limits",
&[
"max_connections_per_ip",
"max_total_connections",
"idle_timeout",
"reaper_interval",
],
),
(
"performance",
&[
"worker_threads",
"imap_pool_size",
"smtp_pool_size",
"read_buffer_kb",
"write_buffer_kb",
],
),
("tls", &["default", "smtp", "imap", "pop3", "jmap"]),
];
pub(crate) fn collect_unknown_toml_keys(raw: &toml::Value) -> Vec<String> {
let mut unknown = Vec::new();
let root = match raw {
toml::Value::Table(t) => t,
_ => return unknown,
};
let known_top: std::collections::HashSet<&str> = KNOWN_TOP_LEVEL_KEYS.iter().copied().collect();
for key in root.keys() {
if !known_top.contains(key.as_str()) {
unknown.push(key.clone());
}
}
for &(section, known_keys) in KNOWN_SECTION_KEYS {
let section_val = match root.get(section) {
Some(v) => v,
None => continue,
};
let section_table = match section_val {
toml::Value::Table(t) => t,
_ => continue,
};
let known_set: std::collections::HashSet<&str> = known_keys.iter().copied().collect();
for key in section_table.keys() {
if !known_set.contains(key.as_str()) {
unknown.push(format!("{}.{}", section, key));
}
}
}
unknown
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_collect_unknown_toml_keys() {
let toml_str = "domain = \"example.com\"\nfoo_bar = \"baz\"\nalpha = 1";
let raw: toml::Value = toml::from_str(toml_str).unwrap();
let unknown = collect_unknown_toml_keys(&raw);
assert!(
unknown.contains(&"foo_bar".to_string()),
"got: {:?}",
unknown
);
assert!(unknown.contains(&"alpha".to_string()), "got: {:?}", unknown);
assert!(
!unknown.contains(&"domain".to_string()),
"got: {:?}",
unknown
);
}
#[test]
fn test_collect_unknown_toml_keys_nested() {
let toml_str = r#"
domain = "example.com"
[smtp]
host = "0.0.0.0"
prot = 25
"#;
let raw: toml::Value = toml::from_str(toml_str).unwrap();
let unknown = collect_unknown_toml_keys(&raw);
assert!(
unknown.contains(&"smtp.prot".to_string()),
"expected 'smtp.prot' in unknown; got: {:?}",
unknown
);
assert!(
!unknown.contains(&"smtp.host".to_string()),
"'smtp.host' is a known field and must not appear; got: {:?}",
unknown
);
}
}