From f738871ad1ff1f1c5c9e78ec5a1b7149c4861a76 Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Sat, 10 Jan 2026 21:18:23 +0800 Subject: [PATCH] fix: replace unsafe unwrap() calls with proper error handling - database/dao/mcp.rs: Use map_err for serde_json serialization - database/dao/providers.rs: Use map_err for settings_config and meta serialization - commands/misc.rs: Use expect() for compile-time regex pattern - services/prompt.rs: Use unwrap_or_default() for SystemTime - deeplink/provider.rs: Replace unwrap() with is_none_or pattern for Option checks Reduces potential panic points from 26 to 1 (static regex init, safe). --- src-tauri/src/commands/misc.rs | 3 +- src-tauri/src/database/dao/mcp.rs | 6 ++- src-tauri/src/database/dao/providers.rs | 15 +++++--- src-tauri/src/deeplink/provider.rs | 51 ++++++++++++------------- src-tauri/src/services/prompt.rs | 2 +- 5 files changed, 41 insertions(+), 36 deletions(-) diff --git a/src-tauri/src/commands/misc.rs b/src-tauri/src/commands/misc.rs index c93462797..af67649cf 100644 --- a/src-tauri/src/commands/misc.rs +++ b/src-tauri/src/commands/misc.rs @@ -145,7 +145,8 @@ async fn fetch_npm_latest_version(client: &reqwest::Client, package: &str) -> Op /// 从版本输出中提取纯版本号 fn extract_version(raw: &str) -> String { // 匹配 semver 格式: x.y.z 或 x.y.z-xxx - let re = regex::Regex::new(r"\d+\.\d+\.\d+(-[\w.]+)?").unwrap(); + let re = regex::Regex::new(r"\d+\.\d+\.\d+(-[\w.]+)?") + .expect("Invalid version regex pattern"); re.find(raw) .map(|m| m.as_str().to_string()) .unwrap_or_else(|| raw.to_string()) diff --git a/src-tauri/src/database/dao/mcp.rs b/src-tauri/src/database/dao/mcp.rs index b9953da26..84cc02210 100644 --- a/src-tauri/src/database/dao/mcp.rs +++ b/src-tauri/src/database/dao/mcp.rs @@ -73,11 +73,13 @@ impl Database { params![ server.id, server.name, - serde_json::to_string(&server.server).unwrap(), + serde_json::to_string(&server.server) + .map_err(|e| AppError::Database(format!("Failed to serialize server config: {e}")))?, server.description, server.homepage, server.docs, - serde_json::to_string(&server.tags).unwrap(), + serde_json::to_string(&server.tags) + .map_err(|e| AppError::Database(format!("Failed to serialize tags: {e}")))?, server.apps.claude, server.apps.codex, server.apps.gemini, diff --git a/src-tauri/src/database/dao/providers.rs b/src-tauri/src/database/dao/providers.rs index 7a78edc48..ede485ced 100644 --- a/src-tauri/src/database/dao/providers.rs +++ b/src-tauri/src/database/dao/providers.rs @@ -220,7 +220,8 @@ impl Database { WHERE id = ?13 AND app_type = ?14", params![ provider.name, - serde_json::to_string(&provider.settings_config).unwrap(), + serde_json::to_string(&provider.settings_config) + .map_err(|e| AppError::Database(format!("Failed to serialize settings_config: {e}")))?, provider.website_url, provider.category, provider.created_at, @@ -228,7 +229,8 @@ impl Database { provider.notes, provider.icon, provider.icon_color, - serde_json::to_string(&meta_clone).unwrap(), + serde_json::to_string(&meta_clone) + .map_err(|e| AppError::Database(format!("Failed to serialize meta: {e}")))?, is_current, in_failover_queue, provider.id, @@ -247,7 +249,8 @@ impl Database { provider.id, app_type, provider.name, - serde_json::to_string(&provider.settings_config).unwrap(), + serde_json::to_string(&provider.settings_config) + .map_err(|e| AppError::Database(format!("Failed to serialize settings_config: {e}")))?, provider.website_url, provider.category, provider.created_at, @@ -255,7 +258,8 @@ impl Database { provider.notes, provider.icon, provider.icon_color, - serde_json::to_string(&meta_clone).unwrap(), + serde_json::to_string(&meta_clone) + .map_err(|e| AppError::Database(format!("Failed to serialize meta: {e}")))?, is_current, in_failover_queue, ], @@ -324,7 +328,8 @@ impl Database { conn.execute( "UPDATE providers SET settings_config = ?1 WHERE id = ?2 AND app_type = ?3", params![ - serde_json::to_string(settings_config).unwrap(), + serde_json::to_string(settings_config) + .map_err(|e| AppError::Database(format!("Failed to serialize settings_config: {e}")))?, provider_id, app_type ], diff --git a/src-tauri/src/deeplink/provider.rs b/src-tauri/src/deeplink/provider.rs index 79ceb3aff..8f2d00b1a 100644 --- a/src-tauri/src/deeplink/provider.rs +++ b/src-tauri/src/deeplink/provider.rs @@ -409,27 +409,26 @@ fn merge_claude_config( })?; // Auto-fill API key if not provided in URL - if request.api_key.is_none() || request.api_key.as_ref().unwrap().is_empty() { + if request.api_key.as_ref().is_none_or(|s| s.is_empty()) { if let Some(token) = env.get("ANTHROPIC_AUTH_TOKEN").and_then(|v| v.as_str()) { request.api_key = Some(token.to_string()); } } // Auto-fill endpoint if not provided in URL - if request.endpoint.is_none() || request.endpoint.as_ref().unwrap().is_empty() { + if request.endpoint.as_ref().is_none_or(|s| s.is_empty()) { if let Some(base_url) = env.get("ANTHROPIC_BASE_URL").and_then(|v| v.as_str()) { request.endpoint = Some(base_url.to_string()); } } // Auto-fill homepage from endpoint if not provided - if (request.homepage.is_none() || request.homepage.as_ref().unwrap().is_empty()) - && request.endpoint.is_some() - && !request.endpoint.as_ref().unwrap().is_empty() - { - request.homepage = infer_homepage_from_endpoint(request.endpoint.as_ref().unwrap()); - if request.homepage.is_none() { - request.homepage = Some("https://anthropic.com".to_string()); + if request.homepage.as_ref().is_none_or(|s| s.is_empty()) { + if let Some(endpoint) = request.endpoint.as_ref().filter(|s| !s.is_empty()) { + request.homepage = infer_homepage_from_endpoint(endpoint); + if request.homepage.is_none() { + request.homepage = Some("https://anthropic.com".to_string()); + } } } @@ -468,7 +467,7 @@ fn merge_codex_config( config: &serde_json::Value, ) -> Result<(), AppError> { // Auto-fill API key from auth.OPENAI_API_KEY - if request.api_key.is_none() || request.api_key.as_ref().unwrap().is_empty() { + if request.api_key.as_ref().is_none_or(|s| s.is_empty()) { if let Some(api_key) = config .get("auth") .and_then(|v| v.get("OPENAI_API_KEY")) @@ -483,7 +482,7 @@ fn merge_codex_config( // Parse TOML config string to extract base_url and model if let Ok(toml_value) = toml::from_str::(config_str) { // Extract base_url from model_providers section - if request.endpoint.is_none() || request.endpoint.as_ref().unwrap().is_empty() { + if request.endpoint.as_ref().is_none_or(|s| s.is_empty()) { if let Some(base_url) = extract_codex_base_url(&toml_value) { request.endpoint = Some(base_url); } @@ -499,13 +498,12 @@ fn merge_codex_config( } // Auto-fill homepage from endpoint - if (request.homepage.is_none() || request.homepage.as_ref().unwrap().is_empty()) - && request.endpoint.is_some() - && !request.endpoint.as_ref().unwrap().is_empty() - { - request.homepage = infer_homepage_from_endpoint(request.endpoint.as_ref().unwrap()); - if request.homepage.is_none() { - request.homepage = Some("https://openai.com".to_string()); + if request.homepage.as_ref().is_none_or(|s| s.is_empty()) { + if let Some(endpoint) = request.endpoint.as_ref().filter(|s| !s.is_empty()) { + request.homepage = infer_homepage_from_endpoint(endpoint); + if request.homepage.is_none() { + request.homepage = Some("https://openai.com".to_string()); + } } } @@ -518,13 +516,13 @@ fn merge_gemini_config( config: &serde_json::Value, ) -> Result<(), AppError> { // Gemini uses flat env structure - if request.api_key.is_none() || request.api_key.as_ref().unwrap().is_empty() { + if request.api_key.as_ref().is_none_or(|s| s.is_empty()) { if let Some(api_key) = config.get("GEMINI_API_KEY").and_then(|v| v.as_str()) { request.api_key = Some(api_key.to_string()); } } - if request.endpoint.is_none() || request.endpoint.as_ref().unwrap().is_empty() { + if request.endpoint.as_ref().is_none_or(|s| s.is_empty()) { if let Some(base_url) = config.get("GEMINI_BASE_URL").and_then(|v| v.as_str()) { request.endpoint = Some(base_url.to_string()); } @@ -538,13 +536,12 @@ fn merge_gemini_config( } // Auto-fill homepage from endpoint - if (request.homepage.is_none() || request.homepage.as_ref().unwrap().is_empty()) - && request.endpoint.is_some() - && !request.endpoint.as_ref().unwrap().is_empty() - { - request.homepage = infer_homepage_from_endpoint(request.endpoint.as_ref().unwrap()); - if request.homepage.is_none() { - request.homepage = Some("https://ai.google.dev".to_string()); + if request.homepage.as_ref().is_none_or(|s| s.is_empty()) { + if let Some(endpoint) = request.endpoint.as_ref().filter(|s| !s.is_empty()) { + request.homepage = infer_homepage_from_endpoint(endpoint); + if request.homepage.is_none() { + request.homepage = Some("https://ai.google.dev".to_string()); + } } } diff --git a/src-tauri/src/services/prompt.rs b/src-tauri/src/services/prompt.rs index 8b7aeabf8..0ed06c4a2 100644 --- a/src-tauri/src/services/prompt.rs +++ b/src-tauri/src/services/prompt.rs @@ -85,7 +85,7 @@ impl PromptService { if !content_exists { let timestamp = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) - .unwrap() + .unwrap_or_default() .as_secs() as i64; let backup_id = format!("backup-{timestamp}"); let backup_prompt = Prompt {