From 27a509362bd3f800fbfa11967e2408002079a941 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Fri, 26 Jun 2026 10:27:40 +0300 Subject: [PATCH] config: warn on unknown keys instead of dropping them silently Signed-off-by: NotAShelf Change-Id: Id06a7e2c96cfdcb69a21fa0416d988696a6a6964 --- Cargo.lock | 11 ++++++++++ Cargo.toml | 1 + src/config.rs | 56 +++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 57 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2d80f2f..09449ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,6 +42,7 @@ dependencies = [ "pound", "rustix", "serde", + "serde_ignored", "smithay-client-toolkit", "thiserror", "toml", @@ -488,6 +489,16 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_ignored" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115dffd5f3853e06e746965a20dcbae6ee747ae30b543d91b0e089668bb07798" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_spanned" version = "1.1.1" diff --git a/Cargo.toml b/Cargo.toml index e7201d8..9208dff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ rustix = { version = "1.1.4", features = [ "fs", ] } serde = { version = "1.0.228", features = ["derive"] } +serde_ignored = "0.1.12" smithay-client-toolkit = "0.20.0" thiserror = "2.0.18" toml = "0.9" diff --git a/src/config.rs b/src/config.rs index 4f335ee..fcdcccb 100644 --- a/src/config.rs +++ b/src/config.rs @@ -195,19 +195,32 @@ impl Config { if !path.exists() { return Self::default(); } - match std::fs::read_to_string(&path) { - Ok(text) => match toml::from_str(&text) { - Ok(config) => { - tracing::info!("loaded config from {}", path.display()); - config - } - Err(err) => { - tracing::warn!("config {}: {err}; using defaults", path.display()); - Self::default() - } - }, + let text = match std::fs::read_to_string(&path) { + Ok(text) => text, Err(err) => { tracing::warn!("read config {}: {err}; using defaults", path.display()); + return Self::default(); + } + }; + // Deserialize through serde_ignored so a typo'd key (`font-sze`) is + // reported instead of silently dropped, while still loading: unknown + // keys stay tolerated, keeping forward-compatibility with newer configs. + let de = match toml::Deserializer::parse(&text) { + Ok(de) => de, + Err(err) => { + tracing::warn!("config {}: {err}; using defaults", path.display()); + return Self::default(); + } + }; + match serde_ignored::deserialize(de, |key| { + tracing::warn!("config {}: unknown key `{key}` ignored", path.display()); + }) { + Ok(config) => { + tracing::info!("loaded config from {}", path.display()); + config + } + Err(err) => { + tracing::warn!("config {}: {err}; using defaults", path.display()); Self::default() } } @@ -253,4 +266,25 @@ mod tests { assert_eq!(c.main.term, "beer"); assert_eq!(c.scrollback.lines, 10_000); } + + #[test] + fn unknown_keys_are_reported_but_still_load() { + let toml = r##" + [main] + font-sze = 14 + font = "JetBrains Mono" + + [made-up] + x = 1 + "##; + let mut unknown = Vec::new(); + let de = toml::Deserializer::parse(toml).unwrap(); + let c: Config = + serde_ignored::deserialize(de, |key| unknown.push(key.to_string())).unwrap(); + // The typo and the bogus table are both surfaced... + assert!(unknown.iter().any(|k| k == "main.font-sze"), "{unknown:?}"); + assert!(unknown.iter().any(|k| k == "made-up"), "{unknown:?}"); + // ...yet the valid key still loaded. + assert_eq!(c.main.font, "JetBrains Mono"); + } }