diff options
| author | Luke Hoersten <[email protected]> | 2026-07-28 18:09:57 -0500 |
|---|---|---|
| committer | Luke Hoersten <[email protected]> | 2026-07-28 18:09:57 -0500 |
| commit | cf5eabb9647e6d53ec29a3f7de599a38dbfd5490 (patch) | |
| tree | 89fcd6f00437c168f042760cc605277a957efc16 /src/main.rs | |
| parent | 4129dcaeceab060d9f1cac599e9e29cab0f44fd7 (diff) | |
Merge the two device state files and reduce mutation
The registry (device names) and service-state (sync results) were two
JSON files keyed the same way, split only for a TypeScript-compatibility
that is moot now that the fabric identity cannot migrate between
implementations. Merge them into one devices.json with a DeviceRecord per
node, which also removes the parallel-map join in status and collapses the
per-command load/store pairs. identity.json stays separate as a write-once
secret.
Also, from a reduction/immutability review:
- Fix with_timeout passing a literal "{what}" instead of the error context
- Drop needless mut: CompactDuration and parse_wall_clock become immutable
expressions; run_status filters with a predicate instead of a mutator;
sync_one extracts write_zone_and_dst; InspectOutcome gains Default plus a
failed() constructor
- Share a join_ids helper and a devices_path helper; add IdentityReport
From; minor combinator tidy-ups
35 unit tests, clippy clean.
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 64 |
1 files changed, 31 insertions, 33 deletions
diff --git a/src/main.rs b/src/main.rs index 16fe2df..f372f8e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -49,7 +49,7 @@ use jiff::Timestamp; use crate::config::{Config, DEFAULT_CONFIG_PATH}; use crate::output::{ConfigReport, DstTransition, NodeListing, Output, StatusReport, SyncReport}; -use crate::state::{NodeRegistry, ServiceState}; +use crate::state::Devices; use crate::time::MatterMicros; #[derive(Parser)] @@ -182,11 +182,10 @@ fn main() -> ExitCode { struct Filter(Option<u64>); impl Filter { - fn retain<V>(&self, map: &mut std::collections::BTreeMap<String, V>) { - if let Some(node) = self.0 { - let key = node.to_string(); - map.retain(|k, _| *k == key); - } + /// Whether a device (keyed by decimal node ID) passes the filter: all + /// devices when no `--node` was given, otherwise just the named one. + fn keeps(&self, key: &str) -> bool { + self.0.is_none_or(|node| key.parse() == Ok(node)) } } @@ -208,9 +207,8 @@ impl Target { /// Targets for read-only interrogation: an empty registry is a valid, /// empty answer. An explicitly requested unknown node is still an error. fn resolve_or_empty(&self, config: &Config) -> anyhow::Result<Vec<u64>> { - let registry: NodeRegistry = - state::load_or_default(&state::registry_path(&config.storage_path)); - let known: Vec<u64> = registry.node_ids().collect(); + let devices: Devices = state::load_or_default(&state::devices_path(&config.storage_path)); + let known: Vec<u64> = devices.node_ids().collect(); match self.0 { None => Ok(known), Some(node) if known.contains(&node) => Ok(vec![node]), @@ -219,41 +217,42 @@ impl Target { if known.is_empty() { "none".into() } else { - known - .iter() - .map(u64::to_string) - .collect::<Vec<_>>() - .join(", ") + join_ids(&known) } ), } } } +/// Comma-joins a slice of displayable values (node IDs) for a message. +fn join_ids<T: std::fmt::Display>(items: &[T]) -> String { + items + .iter() + .map(T::to_string) + .collect::<Vec<_>>() + .join(", ") +} + fn run_status(config: &Config, filter: Filter) -> anyhow::Result<Output> { let tz = config.time_zone(); let now = Timestamp::now(); - let state: ServiceState = - state::load_or_default(&state::service_state_path(&config.storage_path)); - let mut registry: NodeRegistry = - state::load_or_default(&state::registry_path(&config.storage_path)); - filter.retain(&mut registry.nodes); - let nodes: Vec<NodeListing> = registry + let devices: Devices = state::load_or_default(&state::devices_path(&config.storage_path)); + let nodes: Vec<NodeListing> = devices .nodes .into_iter() - .filter_map(|(key, info)| { - // Registry keys are decimal node IDs written by us; anything else - // is file corruption and is skipped, matching the lenient loader. + .filter(|(key, _)| filter.keeps(key)) + .filter_map(|(key, device)| { + // Keys are decimal node IDs written by us; anything else is file + // corruption and is skipped, matching the lenient loader. let node_id: u64 = key.parse().ok()?; - let node = state.nodes.get(&key).cloned().unwrap_or_default(); Some(NodeListing { node_id: node_id.into(), - vendor_name: info.vendor_name, - product_name: info.product_name, - last_successful_connection: node.last_successful_connection, - last_successful_sync: node.last_successful_sync, - last_attempted_sync: node.last_attempted_sync, - last_error: node.last_error, + vendor_name: device.vendor_name, + product_name: device.product_name, + last_successful_connection: device.last_successful_connection, + last_successful_sync: device.last_successful_sync, + last_attempted_sync: device.last_attempted_sync, + last_error: device.last_error, }) }) .collect(); @@ -351,10 +350,9 @@ fn run_commission(config: &Config, code: &str) -> anyhow::Result<Output> { fn run_decommission(config: &Config, target: Target) -> anyhow::Result<Output> { let targets = target.resolve(config)?; let nodes = controller::run(config, controller::DecommissionOp { targets })?; - let registry: NodeRegistry = - state::load_or_default(&state::registry_path(&config.storage_path)); + let devices: Devices = state::load_or_default(&state::devices_path(&config.storage_path)); Ok(Output::Decommission { nodes, - remaining_nodes: registry.nodes.into_keys().collect(), + remaining_nodes: devices.nodes.into_keys().collect(), }) } |
