feat(config): the platform table, read from the markers of the one StaticConfig

platforms::reads(platform, key) says whether a platform reads a top-level key and platforms::unsupported_keys(raw, platform) lists the keys of a configuration text that are keys of komorebi.json but that the platform does not read, which is what a daemon can say at load time instead of dropping them silently. The table is the x-komorebi-platforms markers themselves, so a key cannot be added without saying who reads it. The daemons do not call it yet: reporting at load time is a visible change of behaviour for the user's decision.

The oracle test checks the table against the three checked-in schemas (every key of a tree's schema.json is read by that platform) and on examples.

Verified: native cargo test -p komorebi-config: 4 unit + 9 schemas + 1 doctest passed, 0 failed (tier 1).
This commit is contained in:
LGUG2Z committed 2026-10-31 01:37:54 -07:00
1 parent 657360d358
commit 5763df9f73
3 files changed
+102

No files matched your search

+2
View File
@@ -19,4 +19,6 @@ pub mod changes;
pub mod defaults;
pub mod matching;
pub mod placement;
#[cfg(feature = "schemars")]
pub mod platforms;
pub mod static_config;
+48
View File
@@ -0,0 +1,48 @@
//! Which platform reads which key of `komorebi.json`.
//!
//! The table is not written a second time: it is read from the `x-komorebi-platforms` markers
//! of the one `StaticConfig`, so a key cannot be added without saying who reads it.
use komorebi_protocol::platforms::Platform;
use serde_json::Value;
use crate::schema::PLATFORMS;
use crate::schema::platform_name;
use crate::schema::union;
use crate::static_config::StaticConfig;
/// Whether `platform` reads the top-level `key` of a configuration. A key the type does not
/// know is read by nobody (it is ignored everywhere).
#[must_use]
pub fn reads(platform: Platform, key: &str) -> bool {
let schema = union::<StaticConfig>();
match schema["properties"].get(key) {
None => false,
Some(property) => match property.get(PLATFORMS) {
Some(Value::Array(platforms)) => platforms
.iter()
.any(|p| p.as_str() == Some(platform_name(platform))),
_ => true,
},
}
}
/// The top-level keys of the configuration text `raw` that are keys of `komorebi.json` but that
/// `platform` does not read, in file order: what a daemon says at load time instead of silently
/// dropping them. Keys that are not known at all are not reported.
///
/// # Errors
///
/// The text is not JSON.
pub fn unsupported_keys(raw: &str, platform: Platform) -> color_eyre::Result<Vec<String>> {
let value: Value = serde_json::from_str(raw)?;
let schema = union::<StaticConfig>();
let known = schema["properties"].as_object();
Ok(value
.as_object()
.into_iter()
.flat_map(|object| object.keys())
.filter(|key| known.is_some_and(|k| k.contains_key(*key)) && !reads(platform, key))
.cloned()
.collect())
}
+52
View File
@@ -251,3 +251,55 @@ fn the_schema_of_each_platform_is_a_view_of_the_one_static_config() {
);
}
}
#[test]
fn the_platform_table_comes_from_the_markers() {
use komorebi_config::platforms::reads;
use komorebi_config::platforms::unsupported_keys;
use komorebi_protocol::platforms::Platform;
let windows_only = ["stackbar", "border_style", "slow_application_identifiers"];
let kde_macos_only = [
"border_radius",
"titleless_applications",
"tabbed_applications",
];
let windows_kde = ["transparency", "focus_follows_mouse"];
for key in windows_only {
assert!(reads(Platform::Windows, key));
assert!(
!reads(Platform::Kde, key) && !reads(Platform::MacOs, key),
"{key}"
);
}
for key in kde_macos_only {
assert!(!reads(Platform::Windows, key), "{key}");
assert!(reads(Platform::Kde, key) && reads(Platform::MacOs, key));
}
for key in windows_kde {
assert!(reads(Platform::Windows, key) && reads(Platform::Kde, key));
assert!(!reads(Platform::MacOs, key), "{key}");
}
assert!(reads(Platform::MacOs, "monitors") && !reads(Platform::MacOs, "no_such_key"));
// the keys each tree's schema.json holds are the keys that platform reads
for (tree, platform) in [
(&TREES[0], Platform::Windows),
(&TREES[1], Platform::Kde),
(&TREES[2], Platform::MacOs),
] {
let schema: Value = serde_json::from_str(&checked_in(tree, "schema.json")).unwrap();
for key in schema["properties"].as_object().unwrap().keys() {
assert!(reads(platform, key), "{} reads {key}", tree.name);
}
}
let raw = r#"{ "stackbar": {}, "border": true, "a_key_nobody_knows": 1, "border_radius": 5 }"#;
assert_eq!(unsupported_keys(raw, Platform::Kde).unwrap(), ["stackbar"]);
assert_eq!(
unsupported_keys(raw, Platform::Windows).unwrap(),
["border_radius"]
);
assert!(unsupported_keys("{ ", Platform::Kde).is_err());
}