diff --git a/src-tauri/src/commands/conversations.rs b/src-tauri/src/commands/conversations.rs index 2297ce5..17c04b5 100644 --- a/src-tauri/src/commands/conversations.rs +++ b/src-tauri/src/commands/conversations.rs @@ -136,13 +136,11 @@ pub async fn get_conversation( AgentType::OpenCode => Box::new(OpenCodeParser::new()), AgentType::Gemini => Box::new(GeminiParser::new()), _ => { - return Err( - AppCommandError::new( - AppErrorCode::InvalidInput, - "Conversation parsing is not supported for this agent", - ) - .with_detail(format!("agent_type={agent_type}")), + return Err(AppCommandError::new( + AppErrorCode::InvalidInput, + "Conversation parsing is not supported for this agent", ) + .with_detail(format!("agent_type={agent_type}"))) } }; @@ -178,8 +176,11 @@ pub async fn get_stats() -> Result { }) .await .map_err(|e| { - AppCommandError::new(AppErrorCode::Unknown, "Failed to compute conversation stats") - .with_detail(e.to_string()) + AppCommandError::new( + AppErrorCode::Unknown, + "Failed to compute conversation stats", + ) + .with_detail(e.to_string()) })? } @@ -347,13 +348,11 @@ pub async fn update_conversation_status( conversation_id: i32, status: String, ) -> Result<(), AppCommandError> { - let status_enum: conversation::ConversationStatus = serde_json::from_value( - serde_json::Value::String(status), - ) - .map_err(|e| { - AppCommandError::new(AppErrorCode::InvalidInput, "Invalid conversation status") - .with_detail(e.to_string()) - })?; + let status_enum: conversation::ConversationStatus = + serde_json::from_value(serde_json::Value::String(status)).map_err(|e| { + AppCommandError::new(AppErrorCode::InvalidInput, "Invalid conversation status") + .with_detail(e.to_string()) + })?; conversation_service::update_status(&db.conn, conversation_id, status_enum) .await .map_err(AppCommandError::from) @@ -419,8 +418,7 @@ fn compute_stats(all_conversations: &[ConversationSummary]) -> AgentStats { fn parse_error_to_app_error(error: ParseError) -> AppCommandError { match error { ParseError::ConversationNotFound(id) => { - AppCommandError::new(AppErrorCode::NotFound, "Conversation not found") - .with_detail(id) + AppCommandError::new(AppErrorCode::NotFound, "Conversation not found").with_detail(id) } ParseError::InvalidData(message) => { AppCommandError::new(AppErrorCode::InvalidInput, "Invalid conversation data") @@ -428,10 +426,11 @@ fn parse_error_to_app_error(error: ParseError) -> AppCommandError { } ParseError::Io(err) => AppCommandError::new(AppErrorCode::IoError, "I/O operation failed") .with_detail(err.to_string()), - ParseError::Json(err) => { - AppCommandError::new(AppErrorCode::InvalidInput, "Failed to parse conversation file") - .with_detail(err.to_string()) - } + ParseError::Json(err) => AppCommandError::new( + AppErrorCode::InvalidInput, + "Failed to parse conversation file", + ) + .with_detail(err.to_string()), ParseError::Db(err) => { AppCommandError::new(AppErrorCode::DatabaseError, "Database operation failed") .with_detail(err.to_string()) diff --git a/src-tauri/src/commands/folders.rs b/src-tauri/src/commands/folders.rs index 54ce02f..4158f9b 100644 --- a/src-tauri/src/commands/folders.rs +++ b/src-tauri/src/commands/folders.rs @@ -305,7 +305,7 @@ pub async fn set_folder_parent_branch( db: tauri::State<'_, AppDatabase>, path: String, parent_branch: Option, -) -> Result<(), String> { +) -> Result<(), AppCommandError> { // Find folder by path first use crate::db::entities::folder; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; @@ -314,12 +314,15 @@ pub async fn set_folder_parent_branch( .filter(folder::Column::DeletedAt.is_null()) .one(&db.conn) .await - .map_err(|e| e.to_string())?; + .map_err(|e| { + AppCommandError::new(AppErrorCode::DatabaseError, "Failed to query folder") + .with_detail(e.to_string()) + })?; if let Some(folder_model) = row { folder_service::set_folder_parent_branch(&db.conn, folder_model.id, parent_branch) .await - .map_err(|e| e.to_string())?; + .map_err(AppCommandError::from)?; } Ok(()) } @@ -606,11 +609,10 @@ pub async fn git_worktree_add( .await .map_err(AppCommandError::io)?; if check.status.success() { - return Err(AppCommandError::new( - AppErrorCode::AlreadyExists, - "Branch already exists", - ) - .with_detail(branch_name)); + return Err( + AppCommandError::new(AppErrorCode::AlreadyExists, "Branch already exists") + .with_detail(branch_name), + ); } // 校验目录是否已存在 @@ -810,10 +812,10 @@ pub async fn git_diff_with_branch( if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); - return Err( - AppCommandError::external_command("git diff failed", stderr) - .with_detail(format!("branch={target_branch}")), - ); + return Err(AppCommandError::external_command( + "git diff failed", + format!("branch={target_branch}; {stderr}"), + )); } Ok(String::from_utf8_lossy(&output.stdout).to_string()) @@ -1104,7 +1106,10 @@ pub async fn git_list_all_branches(path: String) -> Result Result { +pub async fn git_merge( + path: String, + branch_name: String, +) -> Result { // Count commits to be merged before performing merge let count_output = crate::process::tokio_command("git") .args(["rev-list", "--count", &format!("HEAD..{}", branch_name)]) @@ -1289,9 +1294,11 @@ fn should_refresh_git_status_for_paths(root_display: &str, changed_paths: &[Stri .any(|path| !ignored.contains(path.as_str())) } -fn canonicalize_watch_root(root: &Path) -> Result<(PathBuf, String), String> { - let canonical = std::fs::canonicalize(root) - .map_err(|e| format!("Unable to resolve workspace root: {e}"))?; +fn canonicalize_watch_root(root: &Path) -> Result<(PathBuf, String), AppCommandError> { + let canonical = std::fs::canonicalize(root).map_err(|e| { + AppCommandError::new(AppErrorCode::NotFound, "Unable to resolve workspace root") + .with_detail(e.to_string()) + })?; let key = normalize_slash_path(&canonical); Ok((canonical, key)) } @@ -1560,18 +1567,27 @@ fn validate_new_name(new_name: &str) -> Result<&str, String> { } #[tauri::command] -pub async fn start_file_tree_watch(app: tauri::AppHandle, root_path: String) -> Result<(), String> { +pub async fn start_file_tree_watch( + app: tauri::AppHandle, + root_path: String, +) -> Result<(), AppCommandError> { let root = PathBuf::from(&root_path); if !root.exists() || !root.is_dir() { - return Err("Folder does not exist".to_string()); + return Err(AppCommandError::new( + AppErrorCode::NotFound, + "Folder does not exist", + )); } let (root_canonical, key) = canonicalize_watch_root(&root)?; { - let mut watchers = FILE_WATCHERS - .lock() - .map_err(|_| "Failed to lock file watcher registry".to_string())?; + let mut watchers = FILE_WATCHERS.lock().map_err(|_| { + AppCommandError::new( + AppErrorCode::Unknown, + "Failed to lock file watcher registry", + ) + })?; if let Some(entry) = watchers.get_mut(&key) { entry.ref_count += 1; return Ok(()); @@ -1606,19 +1622,30 @@ pub async fn start_file_tree_watch(app: tauri::AppHandle, root_path: String) -> } }, ) - .map_err(|e| format!("Failed to create file watcher: {e}"))?, + .map_err(|e| { + AppCommandError::new(AppErrorCode::IoError, "Failed to create file watcher") + .with_detail(e.to_string()) + })?, ); watcher .as_mut() - .ok_or_else(|| "Failed to create file watcher".to_string())? + .ok_or_else(|| { + AppCommandError::new(AppErrorCode::Unknown, "Failed to create file watcher") + })? .watch(&root_canonical, RecursiveMode::Recursive) - .map_err(|e| format!("Failed to start file watcher: {e}"))?; + .map_err(|e| { + AppCommandError::new(AppErrorCode::IoError, "Failed to start file watcher") + .with_detail(e.to_string()) + })?; let should_cleanup_new_watcher = { - let mut watchers = FILE_WATCHERS - .lock() - .map_err(|_| "Failed to lock file watcher registry".to_string())?; + let mut watchers = FILE_WATCHERS.lock().map_err(|_| { + AppCommandError::new( + AppErrorCode::Unknown, + "Failed to lock file watcher registry", + ) + })?; if let Some(entry) = watchers.get_mut(&key) { entry.ref_count += 1; true @@ -1628,9 +1655,12 @@ pub async fn start_file_tree_watch(app: tauri::AppHandle, root_path: String) -> FileWatchEntry { root_canonical, root_display: root_path, - watcher: watcher - .take() - .ok_or_else(|| "Failed to initialize file watcher state".to_string())?, + watcher: watcher.take().ok_or_else(|| { + AppCommandError::new( + AppErrorCode::Unknown, + "Failed to initialize file watcher state", + ) + })?, worker: worker.take(), ref_count: 1, }, @@ -1652,15 +1682,18 @@ pub async fn start_file_tree_watch(app: tauri::AppHandle, root_path: String) -> } #[tauri::command] -pub async fn stop_file_tree_watch(root_path: String) -> Result<(), String> { +pub async fn stop_file_tree_watch(root_path: String) -> Result<(), AppCommandError> { let root = PathBuf::from(&root_path); let key = canonicalize_watch_root(&root) .map(|(_, key)| key) .unwrap_or_else(|_| normalize_slash_path(&root)); - let mut watchers = FILE_WATCHERS - .lock() - .map_err(|_| "Failed to lock file watcher registry".to_string())?; + let mut watchers = FILE_WATCHERS.lock().map_err(|_| { + AppCommandError::new( + AppErrorCode::Unknown, + "Failed to lock file watcher registry", + ) + })?; let target_key = if watchers.contains_key(&key) { Some(key) diff --git a/src-tauri/src/commands/windows.rs b/src-tauri/src/commands/windows.rs index d66f9a0..9721d46 100644 --- a/src-tauri/src/commands/windows.rs +++ b/src-tauri/src/commands/windows.rs @@ -191,13 +191,11 @@ pub async fn focus_folder_window(app: AppHandle, folder_id: i32) -> Result<(), A } } } - Err( - AppCommandError::new( - AppErrorCode::NotFound, - format!("No open window for folder {folder_id}"), - ) - .with_detail(format!("folder_id={folder_id}")), + Err(AppCommandError::new( + AppErrorCode::NotFound, + format!("No open window for folder {folder_id}"), ) + .with_detail(format!("folder_id={folder_id}"))) } #[tauri::command] @@ -251,9 +249,9 @@ pub async fn open_commit_window( } state.set_owner(label.clone(), owner_label); let _ = existing.unminimize(); - existing.set_focus().map_err(|e| { - AppCommandError::window("Failed to focus commit window", e.to_string()) - })?; + existing + .set_focus() + .map_err(|e| AppCommandError::window("Failed to focus commit window", e.to_string()))?; return Ok(()); } diff --git a/src-tauri/src/parsers/claude.rs b/src-tauri/src/parsers/claude.rs index 4ac040e..7791343 100644 --- a/src-tauri/src/parsers/claude.rs +++ b/src-tauri/src/parsers/claude.rs @@ -286,10 +286,7 @@ impl ClaudeParser { } fn resolve_claude_config_dir() -> PathBuf { - resolve_claude_config_dir_from( - std::env::var_os("CLAUDE_CONFIG_DIR"), - dirs::home_dir(), - ) + resolve_claude_config_dir_from(std::env::var_os("CLAUDE_CONFIG_DIR"), dirs::home_dir()) } fn resolve_claude_config_dir_from( @@ -974,10 +971,7 @@ mod tests { #[test] fn claude_config_dir_defaults_to_home_dot_claude() { - let resolved = resolve_claude_config_dir_from( - None, - Some(PathBuf::from("/Users/default")), - ); + let resolved = resolve_claude_config_dir_from(None, Some(PathBuf::from("/Users/default"))); assert_eq!(resolved, PathBuf::from("/Users/default/.claude")); } } diff --git a/src-tauri/src/parsers/codex.rs b/src-tauri/src/parsers/codex.rs index aad13d4..a201866 100644 --- a/src-tauri/src/parsers/codex.rs +++ b/src-tauri/src/parsers/codex.rs @@ -162,10 +162,7 @@ impl CodexParser { } fn resolve_codex_home_dir() -> PathBuf { - resolve_codex_home_dir_from( - std::env::var_os("CODEX_HOME"), - dirs::home_dir(), - ) + resolve_codex_home_dir_from(std::env::var_os("CODEX_HOME"), dirs::home_dir()) } fn resolve_codex_home_dir_from( @@ -1257,10 +1254,7 @@ mod tests { #[test] fn codex_home_defaults_to_home_dot_codex() { - let resolved = resolve_codex_home_dir_from( - None, - Some(PathBuf::from("/Users/default")), - ); + let resolved = resolve_codex_home_dir_from(None, Some(PathBuf::from("/Users/default"))); assert_eq!(resolved, PathBuf::from("/Users/default/.codex")); } } diff --git a/src-tauri/src/parsers/gemini.rs b/src-tauri/src/parsers/gemini.rs index a32f3cd..27a0c35 100644 --- a/src-tauri/src/parsers/gemini.rs +++ b/src-tauri/src/parsers/gemini.rs @@ -466,10 +466,7 @@ impl GeminiParser { } fn resolve_gemini_base_dir() -> PathBuf { - resolve_gemini_base_dir_from( - std::env::var_os("GEMINI_CLI_HOME"), - dirs::home_dir(), - ) + resolve_gemini_base_dir_from(std::env::var_os("GEMINI_CLI_HOME"), dirs::home_dir()) } fn resolve_gemini_base_dir_from( @@ -610,8 +607,8 @@ fn group_into_turns(messages: Vec) -> Vec { #[cfg(test)] mod tests { - use super::GeminiParser; use super::resolve_gemini_base_dir_from; + use super::GeminiParser; use crate::parsers::AgentParser; use std::env; use std::fs; @@ -706,10 +703,7 @@ mod tests { #[test] fn gemini_defaults_to_home_dot_gemini() { - let resolved = resolve_gemini_base_dir_from( - None, - Some(PathBuf::from("/Users/default")), - ); + let resolved = resolve_gemini_base_dir_from(None, Some(PathBuf::from("/Users/default"))); assert_eq!(resolved, PathBuf::from("/Users/default/.gemini")); } } diff --git a/src-tauri/src/parsers/mod.rs b/src-tauri/src/parsers/mod.rs index bd4a3c0..d67154f 100644 --- a/src-tauri/src/parsers/mod.rs +++ b/src-tauri/src/parsers/mod.rs @@ -251,8 +251,8 @@ mod tests { use chrono::Utc; use super::{ - infer_context_window_max_tokens, latest_turn_total_usage_tokens, merge_context_window_stats, - path_eq_for_matching, + infer_context_window_max_tokens, latest_turn_total_usage_tokens, + merge_context_window_stats, path_eq_for_matching, }; use crate::models::{MessageTurn, SessionStats, TurnRole, TurnUsage}; diff --git a/src-tauri/src/parsers/opencode.rs b/src-tauri/src/parsers/opencode.rs index af727e2..d1e1af2 100644 --- a/src-tauri/src/parsers/opencode.rs +++ b/src-tauri/src/parsers/opencode.rs @@ -634,13 +634,7 @@ mod tests { #[test] fn xdg_data_home_falls_back_to_home_local_share() { - let resolved = resolve_xdg_data_home( - None, - Some(PathBuf::from("/Users/default")), - ); - assert_eq!( - resolved, - Some(PathBuf::from("/Users/default/.local/share")) - ); + let resolved = resolve_xdg_data_home(None, Some(PathBuf::from("/Users/default"))); + assert_eq!(resolved, Some(PathBuf::from("/Users/default/.local/share"))); } }