From da4f7b5fe4bc5e052f3861536f7c9b30c7aa9cff Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 5 Sep 2025 21:03:11 +0800 Subject: [PATCH] refactor(codex): unify TOML handling with robust error recovery - Extract common TOML read/validation logic into dedicated helper functions - Add graceful degradation for corrupted config files during migration - Centralize Codex config.toml processing to ensure consistency across operations - Improve error handling: log warnings for invalid files but continue processing - Eliminate code duplication between migration, import, and runtime operations This makes the system more resilient to user configuration issues while maintaining data integrity through unified validation logic. --- src-tauri/src/codex_config.rs | 42 +++++++++++++++++++++++++++++++++++ src-tauri/src/commands.rs | 14 +++--------- src-tauri/src/migration.rs | 36 ++++++++++-------------------- 3 files changed, 57 insertions(+), 35 deletions(-) diff --git a/src-tauri/src/codex_config.rs b/src-tauri/src/codex_config.rs index 3556585..f525156 100644 --- a/src-tauri/src/codex_config.rs +++ b/src-tauri/src/codex_config.rs @@ -5,6 +5,7 @@ use crate::config::{ atomic_write, delete_file, sanitize_provider_name, write_json_file, write_text_file, }; use std::fs; +use std::path::Path; use serde_json::Value; /// 获取 Codex 配置目录路径 @@ -95,3 +96,44 @@ pub fn write_codex_live_atomic(auth: &Value, config_text_opt: Option<&str>) -> R Ok(()) } + +/// 读取 `~/.codex/config.toml`,若不存在返回空字符串 +pub fn read_codex_config_text() -> Result { + let path = get_codex_config_path(); + if path.exists() { + std::fs::read_to_string(&path).map_err(|e| format!("读取 config.toml 失败: {}", e)) + } else { + Ok(String::new()) + } +} + +/// 从给定路径读取 config.toml 文本(路径存在时);路径不存在则返回空字符串 +pub fn read_config_text_from_path(path: &Path) -> Result { + if path.exists() { + std::fs::read_to_string(path).map_err(|e| format!("读取 {} 失败: {}", path.display(), e)) + } else { + Ok(String::new()) + } +} + +/// 对非空的 TOML 文本进行语法校验 +pub fn validate_config_toml(text: &str) -> Result<(), String> { + if text.trim().is_empty() { + return Ok(()); + } + toml::from_str::(text).map(|_| ()).map_err(|e| format!("config.toml 语法错误: {}", e)) +} + +/// 读取并校验 `~/.codex/config.toml`,返回文本(可能为空) +pub fn read_and_validate_codex_config_text() -> Result { + let s = read_codex_config_text()?; + validate_config_toml(&s)?; + Ok(s) +} + +/// 从指定路径读取并校验 config.toml,返回文本(可能为空) +pub fn read_and_validate_config_from_path(path: &Path) -> Result { + let s = read_config_text_from_path(path)?; + validate_config_toml(&s)?; + Ok(s) +} diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 59a11d3..bfd706a 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -387,21 +387,13 @@ pub async fn import_default_config( let settings_config = match app_type { AppType::Codex => { let auth_path = codex_config::get_codex_auth_path(); - let config_path = codex_config::get_codex_config_path(); if !auth_path.exists() { return Err("Codex 配置文件不存在".to_string()); } let auth: serde_json::Value = crate::config::read_json_file::(&auth_path)?; - let config_str = if config_path.exists() { - let s = std::fs::read_to_string(&config_path) - .map_err(|e| format!("读取 config.toml 失败: {}", e))?; - if !s.trim().is_empty() { - toml::from_str::(&s) - .map_err(|e| format!("config.toml 语法错误: {}", e))?; - } - s - } else { - String::new() + let config_str = match crate::codex_config::read_and_validate_codex_config_text() { + Ok(s) => s, + Err(e) => return Err(e), }; serde_json::json!({ "auth": auth, "config": config_str }) } diff --git a/src-tauri/src/migration.rs b/src-tauri/src/migration.rs index 6d3cae1..c440383 100644 --- a/src-tauri/src/migration.rs +++ b/src-tauri/src/migration.rs @@ -116,16 +116,16 @@ fn scan_codex_copies() -> Vec<(String, Option, Option, Value)> if let Some(authp) = auth_path { if let Ok(auth) = crate::config::read_json_file::(&authp) { let config_str = if let Some(cfgp) = &config_path { - fs::read_to_string(cfgp).unwrap_or_default() + match crate::codex_config::read_and_validate_config_from_path(cfgp) { + Ok(s) => s, + Err(e) => { + log::warn!("跳过无效 Codex config-{}.toml: {}", name, e); + String::new() + } + } } else { String::new() }; - // 校验 TOML(若非空) - if !config_str.trim().is_empty() { - if let Err(e) = toml::from_str::(&config_str) { - log::warn!("跳过无效 Codex config-{}.toml: {}", name, e); - } - } let settings = serde_json::json!({ "auth": auth, "config": config_str, @@ -247,27 +247,15 @@ pub fn migrate_copies_into_config(config: &mut MultiAppConfig) -> Result = { let auth_path = crate::codex_config::get_codex_auth_path(); - let config_path = crate::codex_config::get_codex_config_path(); if auth_path.exists() { match crate::config::read_json_file::(&auth_path) { Ok(auth) => { - let cfg = if config_path.exists() { - match std::fs::read_to_string(&config_path) { - Ok(s) => { - if !s.trim().is_empty() { - if let Err(e) = toml::from_str::(&s) { - log::warn!("Codex live config.toml 语法错误: {}", e); - } - } - s - } - Err(e) => { - log::warn!("读取 Codex live config.toml 失败: {}", e); - String::new() - } + let cfg = match crate::codex_config::read_and_validate_codex_config_text() { + Ok(s) => s, + Err(e) => { + log::warn!("读取/校验 Codex live config.toml 失败: {}", e); + String::new() } - } else { - String::new() }; Some(("default".to_string(), serde_json::json!({"auth": auth, "config": cfg}))) }