commit 52244c296262d1f585bb9344a874d013cc5acc73
parent 3b424bd5bc014634a0ca1e546ccd0105ccad9f30
Author: triesap <triesap@radroots.dev>
Date: Mon, 19 Jan 2026 18:28:27 +0000
app: add health check types
- define health status and result structs
- add health report container with defaults
- export health types from app crate
- add unit tests for status and defaults
Diffstat:
3 files changed, 114 insertions(+), 1 deletion(-)
diff --git a/app/src/app.rs b/app/src/app.rs
@@ -47,7 +47,7 @@ pub fn App() -> impl IntoView {
AppInitStage::Geocoder => "orange",
AppInitStage::Idle => "gray",
};
- let reset_disabled = move || backends.with_untracked(|value| value.is_none());
+ let reset_disabled = move || backends.with(|value| value.is_none());
let reset_label = move || {
reset_status
.get()
diff --git a/app/src/health.rs b/app/src/health.rs
@@ -0,0 +1,107 @@
+#![forbid(unsafe_code)]
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum AppHealthCheckStatus {
+ Ok,
+ Error,
+ Skipped,
+}
+
+impl AppHealthCheckStatus {
+ pub const fn as_str(self) -> &'static str {
+ match self {
+ AppHealthCheckStatus::Ok => "ok",
+ AppHealthCheckStatus::Error => "error",
+ AppHealthCheckStatus::Skipped => "skipped",
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct AppHealthCheckResult {
+ pub status: AppHealthCheckStatus,
+ pub message: Option<String>,
+}
+
+impl AppHealthCheckResult {
+ pub fn ok() -> Self {
+ Self {
+ status: AppHealthCheckStatus::Ok,
+ message: None,
+ }
+ }
+
+ pub fn error(message: impl Into<String>) -> Self {
+ Self {
+ status: AppHealthCheckStatus::Error,
+ message: Some(message.into()),
+ }
+ }
+
+ pub fn skipped() -> Self {
+ Self {
+ status: AppHealthCheckStatus::Skipped,
+ message: None,
+ }
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct AppHealthReport {
+ pub key_maps: AppHealthCheckResult,
+ pub bootstrap_config: AppHealthCheckResult,
+ pub bootstrap_app_data: AppHealthCheckResult,
+ pub datastore_roundtrip: AppHealthCheckResult,
+ pub keystore: AppHealthCheckResult,
+}
+
+impl Default for AppHealthReport {
+ fn default() -> Self {
+ Self {
+ key_maps: AppHealthCheckResult::skipped(),
+ bootstrap_config: AppHealthCheckResult::skipped(),
+ bootstrap_app_data: AppHealthCheckResult::skipped(),
+ datastore_roundtrip: AppHealthCheckResult::skipped(),
+ keystore: AppHealthCheckResult::skipped(),
+ }
+ }
+}
+
+impl AppHealthReport {
+ pub fn empty() -> Self {
+ Self::default()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{AppHealthCheckResult, AppHealthCheckStatus, AppHealthReport};
+
+ #[test]
+ fn health_status_as_str() {
+ assert_eq!(AppHealthCheckStatus::Ok.as_str(), "ok");
+ assert_eq!(AppHealthCheckStatus::Error.as_str(), "error");
+ assert_eq!(AppHealthCheckStatus::Skipped.as_str(), "skipped");
+ }
+
+ #[test]
+ fn health_result_constructors() {
+ let ok = AppHealthCheckResult::ok();
+ assert_eq!(ok.status, AppHealthCheckStatus::Ok);
+ assert!(ok.message.is_none());
+
+ let err = AppHealthCheckResult::error("boom");
+ assert_eq!(err.status, AppHealthCheckStatus::Error);
+ assert_eq!(err.message.as_deref(), Some("boom"));
+ }
+
+ #[test]
+ fn health_report_defaults_skipped() {
+ let report = AppHealthReport::default();
+ assert_eq!(report.key_maps.status, AppHealthCheckStatus::Skipped);
+ assert_eq!(report.bootstrap_config.status, AppHealthCheckStatus::Skipped);
+ assert_eq!(report.bootstrap_app_data.status, AppHealthCheckStatus::Skipped);
+ assert_eq!(report.datastore_roundtrip.status, AppHealthCheckStatus::Skipped);
+ assert_eq!(report.keystore.status, AppHealthCheckStatus::Skipped);
+ }
+}
diff --git a/app/src/lib.rs b/app/src/lib.rs
@@ -5,6 +5,7 @@ mod bootstrap;
mod context;
mod config;
mod data;
+mod health;
mod init;
mod entry;
@@ -18,6 +19,11 @@ pub use bootstrap::{
};
pub use context::{app_context, AppContext};
pub use data::{AppAppData, AppConfigData, AppConfigRole};
+pub use health::{
+ AppHealthCheckResult,
+ AppHealthCheckStatus,
+ AppHealthReport,
+};
pub use config::{
app_config_default,
app_config_from_env,