fix: prevent silent config fallback and data loss on startup
This commit introduces fail-fast error handling for config loading failures, replacing the previous silent fallback to default config which could cause data loss (e.g., all user providers disappearing). Key changes: Backend (Rust): - Replace AppState::new() with AppState::try_new() to explicitly propagate errors - Remove Default trait to prevent accidental empty state creation - Add init_status module as global error cache (OnceLock + RwLock) - Implement dual-channel error notification: 1. Event emission (low-latency, may race with frontend subscription) 2. Command-based polling (reliable, guaranteed delivery) - Remove unconditional save on startup to prevent overwriting corrupted config Frontend (TypeScript): - Add event listener for "configLoadError" (fast path) - Add bootstrap-time polling via get_init_error command (reliable path) - Display detailed error dialog with recovery instructions - Prompt user to exit for manual repair Impact: - First-time users: No change (load() returns Ok(default) when file missing) - Corrupted config: Application shows error and exits gracefully - Prevents accidental config overwrite during initialization Fixes the only critical issue identified in previous code review (silent fallback causing data loss).
This commit is contained in:
42
src-tauri/src/init_status.rs
Normal file
42
src-tauri/src/init_status.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use serde::Serialize;
|
||||
use std::sync::{OnceLock, RwLock};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct InitErrorPayload {
|
||||
pub path: String,
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
static INIT_ERROR: OnceLock<RwLock<Option<InitErrorPayload>>> = OnceLock::new();
|
||||
|
||||
fn cell() -> &'static RwLock<Option<InitErrorPayload>> {
|
||||
INIT_ERROR.get_or_init(|| RwLock::new(None))
|
||||
}
|
||||
|
||||
pub fn set_init_error(payload: InitErrorPayload) {
|
||||
if let Ok(mut guard) = cell().write() {
|
||||
*guard = Some(payload);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_init_error() -> Option<InitErrorPayload> {
|
||||
cell().read().ok()?.clone()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn init_error_roundtrip() {
|
||||
let payload = InitErrorPayload {
|
||||
path: "/tmp/config.json".into(),
|
||||
error: "broken json".into(),
|
||||
};
|
||||
set_init_error(payload.clone());
|
||||
let got = get_init_error().expect("should get payload back");
|
||||
assert_eq!(got.path, payload.path);
|
||||
assert_eq!(got.error, payload.error);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user