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.
This commit is contained in:
@@ -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<String, String> {
|
||||
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<String, String> {
|
||||
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::<toml::Table>(text).map(|_| ()).map_err(|e| format!("config.toml 语法错误: {}", e))
|
||||
}
|
||||
|
||||
/// 读取并校验 `~/.codex/config.toml`,返回文本(可能为空)
|
||||
pub fn read_and_validate_codex_config_text() -> Result<String, String> {
|
||||
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<String, String> {
|
||||
let s = read_config_text_from_path(path)?;
|
||||
validate_config_toml(&s)?;
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
@@ -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::<serde_json::Value>(&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::<toml::Table>(&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 })
|
||||
}
|
||||
|
||||
@@ -116,16 +116,16 @@ fn scan_codex_copies() -> Vec<(String, Option<PathBuf>, Option<PathBuf>, Value)>
|
||||
if let Some(authp) = auth_path {
|
||||
if let Ok(auth) = crate::config::read_json_file::<Value>(&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::<toml::Table>(&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<bool, S
|
||||
// 读取 live:Codex(auth.json 必需,config.toml 可空)
|
||||
let live_codex: Option<(String, Value)> = {
|
||||
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::<Value>(&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::<toml::Table>(&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})))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user