129 lines
4.1 KiB
Rust
129 lines
4.1 KiB
Rust
//! Parser for `core.config_entries` (HA storage schema v1, minor_version varies).
|
|
//!
|
|
//! Per ADR-165 §6 Q5, `.storage/core.config_entries` format is undocumented
|
|
//! and version-gated. P1 reads the envelope and emits:
|
|
//! - count of config entries
|
|
//! - list of integration domains represented
|
|
//!
|
|
//! Conversion to HOMECORE plugin manifests is P2.
|
|
//!
|
|
//! Note: `config_entries` uses a different `minor_version` track from
|
|
//! `entity_registry`. As of HA 2025.1 it is typically minor_version=1 or 2.
|
|
//! We accept any minor_version ≤ MAX_SUPPORTED_MINOR and hard-error above it.
|
|
|
|
use std::path::Path;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{storage::read_envelope, MigrateError};
|
|
|
|
/// Maximum `minor_version` we claim to understand for config_entries.
|
|
const MAX_SUPPORTED_MINOR: u32 = 4;
|
|
|
|
/// Diagnostic summary produced by P1 inspection.
|
|
#[derive(Clone, Debug, Serialize)]
|
|
pub struct ConfigEntriesSummary {
|
|
pub count: usize,
|
|
pub domains: Vec<String>,
|
|
}
|
|
|
|
/// Minimal fields we read from each config-entry row.
|
|
#[derive(Debug, Deserialize)]
|
|
struct HaConfigEntryRow {
|
|
domain: String,
|
|
#[allow(dead_code)]
|
|
entry_id: String,
|
|
/// Title shown in HA UI (informational only in P1).
|
|
#[serde(default)]
|
|
#[allow(dead_code)]
|
|
title: Option<String>,
|
|
/// Source of the entry: "user" | "discovery" | "import" etc.
|
|
#[serde(default)]
|
|
#[allow(dead_code)]
|
|
source: Option<String>,
|
|
/// State: "loaded" | "setup_error" etc.
|
|
#[serde(default)]
|
|
#[allow(dead_code)]
|
|
state: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct HaConfigEntriesData {
|
|
entries: Vec<HaConfigEntryRow>,
|
|
}
|
|
|
|
/// Read `core.config_entries` from `path` and return a diagnostic summary.
|
|
pub fn inspect_config_entries(path: &Path) -> Result<ConfigEntriesSummary, MigrateError> {
|
|
let env = read_envelope(path)?;
|
|
let file_str = path.display().to_string();
|
|
|
|
// config_entries has version=1 and minor_version in 1..MAX_SUPPORTED_MINOR.
|
|
if env.version != 1 || env.minor_version > MAX_SUPPORTED_MINOR {
|
|
return Err(MigrateError::UnsupportedSchemaVersion {
|
|
file: file_str.clone(),
|
|
version: env.version,
|
|
minor_version: env.minor_version,
|
|
});
|
|
}
|
|
|
|
let data: HaConfigEntriesData =
|
|
serde_json::from_value(env.data).map_err(|e| MigrateError::JsonParse {
|
|
path: file_str,
|
|
source: e,
|
|
})?;
|
|
|
|
let mut domains: Vec<String> = data.entries.iter().map(|e| e.domain.clone()).collect();
|
|
domains.sort();
|
|
domains.dedup();
|
|
|
|
Ok(ConfigEntriesSummary {
|
|
count: data.entries.len(),
|
|
domains,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::io::Write;
|
|
use tempfile::NamedTempFile;
|
|
|
|
const FIXTURE: &str = r#"{
|
|
"version": 1,
|
|
"minor_version": 1,
|
|
"key": "core.config_entries",
|
|
"data": {
|
|
"entries": [
|
|
{"domain": "hue", "entry_id": "ce_001", "title": "Philips Hue", "source": "user", "state": "loaded"},
|
|
{"domain": "zha", "entry_id": "ce_002", "title": "ZHA", "source": "user", "state": "loaded"},
|
|
{"domain": "hue", "entry_id": "ce_003", "title": "Hue 2", "source": "user", "state": "setup_error"}
|
|
]
|
|
}
|
|
}"#;
|
|
|
|
#[test]
|
|
fn inspect_emits_count_and_domains() {
|
|
let mut f = NamedTempFile::new().unwrap();
|
|
f.write_all(FIXTURE.as_bytes()).unwrap();
|
|
let summary = inspect_config_entries(f.path()).unwrap();
|
|
assert_eq!(summary.count, 3);
|
|
assert_eq!(summary.domains, vec!["hue", "zha"]);
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_minor_version_hard_errors() {
|
|
let json = r#"{
|
|
"version": 1, "minor_version": 99,
|
|
"key": "core.config_entries",
|
|
"data": {"entries": []}
|
|
}"#;
|
|
let mut f = NamedTempFile::new().unwrap();
|
|
f.write_all(json.as_bytes()).unwrap();
|
|
let err = inspect_config_entries(f.path()).unwrap_err();
|
|
assert!(matches!(
|
|
err,
|
|
MigrateError::UnsupportedSchemaVersion { minor_version: 99, .. }
|
|
));
|
|
}
|
|
}
|