Files
cc-switch/src-tauri/src/error.rs
Jason 5c3aca18eb refactor(backend): implement transaction mechanism and i18n errors for provider service
This commit completes phase 4 service layer extraction by introducing:

1. **Transaction mechanism with 2PC (Two-Phase Commit)**:
   - Introduced `run_transaction()` wrapper with snapshot-based rollback
   - Implemented `LiveSnapshot` enum to capture and restore live config files
   - Added `PostCommitAction` to separate config.json persistence from live file writes
   - Applied to critical operations: add, update, switch providers
   - Ensures atomicity: memory + config.json + live files stay consistent

2. **Internationalized error handling**:
   - Added `AppError::Localized` variant with key + zh + en messages
   - Implemented `AppError::localized()` helper function
   - Migrated 24 error sites to use i18n-ready errors
   - Enables frontend to display errors in user's preferred language

3. **Concurrency optimization**:
   - Fixed `get_custom_endpoints()` to use read lock instead of write lock
   - Ensured async IO operations (usage query) execute outside lock scope
   - Added defensive RAII lock management with explicit scope blocks

4. **Code organization improvements**:
   - Reduced commands/provider.rs from ~800 to ~320 lines (-60%)
   - Expanded services/provider.rs with transaction infrastructure
   - Added unit tests for validation and credential extraction
   - Documented legacy file cleanup logic with inline comments

5. **Backfill mechanism refinement**:
   - Ensured live config is synced back to memory before switching
   - Maintains SSOT (Single Source of Truth) architecture principle
   - Handles Codex dual-file (auth.json + config.toml) atomically

Breaking changes: None (internal refactoring only)
Performance: Improved read concurrency, no measurable overhead from snapshots
Test coverage: Added validation tests, updated service layer tests
2025-10-28 17:47:15 +08:00

99 lines
2.2 KiB
Rust

use std::path::Path;
use std::sync::PoisonError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
#[error("配置错误: {0}")]
Config(String),
#[error("无效输入: {0}")]
InvalidInput(String),
#[error("IO 错误: {path}: {source}")]
Io {
path: String,
#[source]
source: std::io::Error,
},
#[error("{context}: {source}")]
IoContext {
context: String,
#[source]
source: std::io::Error,
},
#[error("JSON 解析错误: {path}: {source}")]
Json {
path: String,
#[source]
source: serde_json::Error,
},
#[error("JSON 序列化失败: {source}")]
JsonSerialize {
#[source]
source: serde_json::Error,
},
#[error("TOML 解析错误: {path}: {source}")]
Toml {
path: String,
#[source]
source: toml::de::Error,
},
#[error("锁获取失败: {0}")]
Lock(String),
#[error("供应商不存在: {0}")]
ProviderNotFound(String),
#[error("MCP 校验失败: {0}")]
McpValidation(String),
#[error("{0}")]
Message(String),
#[error("{zh} ({en})")]
Localized {
key: &'static str,
zh: String,
en: String,
},
}
impl AppError {
pub fn io(path: impl AsRef<Path>, source: std::io::Error) -> Self {
Self::Io {
path: path.as_ref().display().to_string(),
source,
}
}
pub fn json(path: impl AsRef<Path>, source: serde_json::Error) -> Self {
Self::Json {
path: path.as_ref().display().to_string(),
source,
}
}
pub fn toml(path: impl AsRef<Path>, source: toml::de::Error) -> Self {
Self::Toml {
path: path.as_ref().display().to_string(),
source,
}
}
pub fn localized(key: &'static str, zh: impl Into<String>, en: impl Into<String>) -> Self {
Self::Localized {
key,
zh: zh.into(),
en: en.into(),
}
}
}
impl<T> From<PoisonError<T>> for AppError {
fn from(err: PoisonError<T>) -> Self {
Self::Lock(err.to_string())
}
}
impl From<AppError> for String {
fn from(err: AppError) -> Self {
err.to_string()
}
}