From 590be4e1361c325ed22676595b00b93fc94c6fb7 Mon Sep 17 00:00:00 2001 From: Jason Date: Wed, 29 Oct 2025 20:33:30 +0800 Subject: [PATCH] refactor(providers): add flexible app type resolution with dual parameter support Add `resolve_app_type` helper to support both enum and string-based app type parameters across all provider commands. This change: - Eliminates implicit default to Claude (previously used `unwrap_or`) - Supports two parameter forms: `app_type` (enum, priority 1) and `app` (string, priority 2) - Provides explicit error handling when both parameters are missing - Updates all 14 provider command functions with consistent parameter validation - Fixes tray menu provider switching to pass the new `app` parameter This dual-parameter approach maintains backward compatibility while enabling future CLI tool integration and more flexible API usage patterns. Technical details: - Priority order: `app_type` enum > `app` string > error - Invalid `app` strings now return errors instead of defaulting - All existing tests pass (45/45) --- src-tauri/src/commands/provider.rs | 78 +++++++++++++++++++++--------- src-tauri/src/lib.rs | 7 ++- src/lib/api/providers.ts | 19 +++----- 3 files changed, 69 insertions(+), 35 deletions(-) diff --git a/src-tauri/src/commands/provider.rs b/src-tauri/src/commands/provider.rs index e365c79..0819075 100644 --- a/src-tauri/src/commands/provider.rs +++ b/src-tauri/src/commands/provider.rs @@ -11,14 +11,43 @@ fn missing_param(param: &str) -> String { format!("缺少 {} 参数 (Missing {} parameter)", param, param) } +fn resolve_app_type(app_type: Option, app: Option) -> Result { + match (app_type, app) { + (Some(at), None) => Ok(at), + (None, Some(a)) => match a.to_lowercase().as_str() { + "claude" => Ok(AppType::Claude), + "codex" => Ok(AppType::Codex), + other => Err(format!( + "params.invalid: 无效的 app 值: {} (Invalid app)", + other + )), + }, + (Some(at), Some(a)) => { + let a_norm = a.to_lowercase(); + let at_norm = at.as_str().to_string(); + if a_norm == at_norm { + // 接受但提示:建议仅传 app + log::warn!("params.deprecated: 同时传递 app 与 app_type,建议仅使用 app"); + Ok(at) + } else { + Err(format!( + "params.conflict: app 与 app_type 冲突 (app={}, app_type={})", + a_norm, at_norm + )) + } + } + (None, None) => Err(missing_param("app")), + } +} + /// 获取所有供应商 #[tauri::command] pub fn get_providers( state: State<'_, AppState>, app_type: Option, + app: Option, ) -> Result, String> { - let app_type = app_type.unwrap_or(AppType::Claude); - + let app_type = resolve_app_type(app_type, app)?; ProviderService::list(state.inner(), app_type).map_err(|e| e.to_string()) } @@ -27,9 +56,9 @@ pub fn get_providers( pub fn get_current_provider( state: State<'_, AppState>, app_type: Option, + app: Option, ) -> Result { - let app_type = app_type.unwrap_or(AppType::Claude); - + let app_type = resolve_app_type(app_type, app)?; ProviderService::current(state.inner(), app_type).map_err(|e| e.to_string()) } @@ -38,10 +67,10 @@ pub fn get_current_provider( pub fn add_provider( state: State<'_, AppState>, app_type: Option, + app: Option, provider: Provider, ) -> Result { - let app_type = app_type.unwrap_or(AppType::Claude); - + let app_type = resolve_app_type(app_type, app)?; ProviderService::add(state.inner(), app_type, provider).map_err(|e| e.to_string()) } @@ -50,10 +79,10 @@ pub fn add_provider( pub fn update_provider( state: State<'_, AppState>, app_type: Option, + app: Option, provider: Provider, ) -> Result { - let app_type = app_type.unwrap_or(AppType::Claude); - + let app_type = resolve_app_type(app_type, app)?; ProviderService::update(state.inner(), app_type, provider).map_err(|e| e.to_string()) } @@ -62,10 +91,10 @@ pub fn update_provider( pub fn delete_provider( state: State<'_, AppState>, app_type: Option, + app: Option, id: String, ) -> Result { - let app_type = app_type.unwrap_or(AppType::Claude); - + let app_type = resolve_app_type(app_type, app)?; ProviderService::delete(state.inner(), app_type, &id) .map(|_| true) .map_err(|e| e.to_string()) @@ -89,10 +118,10 @@ pub fn switch_provider_test_hook( pub fn switch_provider( state: State<'_, AppState>, app_type: Option, + app: Option, id: String, ) -> Result { - let app_type = app_type.unwrap_or(AppType::Claude); - + let app_type = resolve_app_type(app_type, app)?; switch_provider_internal(&state, app_type, &id) .map(|_| true) .map_err(|e| e.to_string()) @@ -115,9 +144,9 @@ pub fn import_default_config_test_hook( pub fn import_default_config( state: State<'_, AppState>, app_type: Option, + app: Option, ) -> Result { - let app_type = app_type.unwrap_or(AppType::Claude); - + let app_type = resolve_app_type(app_type, app)?; import_default_config_internal(&state, app_type) .map(|_| true) .map_err(Into::into) @@ -129,11 +158,10 @@ pub async fn query_provider_usage( state: State<'_, AppState>, provider_id: Option, app_type: Option, + app: Option, ) -> Result { let provider_id = provider_id.ok_or_else(|| missing_param("providerId"))?; - - let app_type = app_type.unwrap_or(AppType::Claude); - + let app_type = resolve_app_type(app_type, app)?; ProviderService::query_usage(state.inner(), app_type, &provider_id) .await .map_err(|e| e.to_string()) @@ -163,9 +191,10 @@ pub async fn test_api_endpoints( pub fn get_custom_endpoints( state: State<'_, AppState>, app_type: Option, + app: Option, provider_id: Option, ) -> Result, String> { - let app_type = app_type.unwrap_or(AppType::Claude); + let app_type = resolve_app_type(app_type, app)?; let provider_id = provider_id.ok_or_else(|| missing_param("providerId"))?; ProviderService::get_custom_endpoints(state.inner(), app_type, &provider_id) .map_err(|e| e.to_string()) @@ -176,10 +205,11 @@ pub fn get_custom_endpoints( pub fn add_custom_endpoint( state: State<'_, AppState>, app_type: Option, + app: Option, provider_id: Option, url: String, ) -> Result<(), String> { - let app_type = app_type.unwrap_or(AppType::Claude); + let app_type = resolve_app_type(app_type, app)?; let provider_id = provider_id.ok_or_else(|| missing_param("providerId"))?; ProviderService::add_custom_endpoint(state.inner(), app_type, &provider_id, url) .map_err(|e| e.to_string()) @@ -190,10 +220,11 @@ pub fn add_custom_endpoint( pub fn remove_custom_endpoint( state: State<'_, AppState>, app_type: Option, + app: Option, provider_id: Option, url: String, ) -> Result<(), String> { - let app_type = app_type.unwrap_or(AppType::Claude); + let app_type = resolve_app_type(app_type, app)?; let provider_id = provider_id.ok_or_else(|| missing_param("providerId"))?; ProviderService::remove_custom_endpoint(state.inner(), app_type, &provider_id, url) .map_err(|e| e.to_string()) @@ -204,10 +235,11 @@ pub fn remove_custom_endpoint( pub fn update_endpoint_last_used( state: State<'_, AppState>, app_type: Option, + app: Option, provider_id: Option, url: String, ) -> Result<(), String> { - let app_type = app_type.unwrap_or(AppType::Claude); + let app_type = resolve_app_type(app_type, app)?; let provider_id = provider_id.ok_or_else(|| missing_param("providerId"))?; ProviderService::update_endpoint_last_used(state.inner(), app_type, &provider_id, url) .map_err(|e| e.to_string()) @@ -218,9 +250,9 @@ pub fn update_endpoint_last_used( pub fn update_providers_sort_order( state: State<'_, AppState>, app_type: Option, + app: Option, updates: Vec, ) -> Result { - let app_type = app_type.unwrap_or(AppType::Claude); - + let app_type = resolve_app_type(app_type, app)?; ProviderService::update_sort_order(state.inner(), app_type, updates).map_err(|e| e.to_string()) } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 133d8dc..4b37b46 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -267,7 +267,12 @@ fn switch_provider_internal( let app_type_str = app_type.as_str().to_string(); let provider_id_clone = provider_id.clone(); - crate::commands::switch_provider(app_state.clone(), Some(app_type), provider_id) + crate::commands::switch_provider( + app_state.clone(), + Some(app_type), + Some(app_type_str.clone()), + provider_id, + ) .map_err(AppError::Message)?; // 切换成功后重新创建托盘菜单 diff --git a/src/lib/api/providers.ts b/src/lib/api/providers.ts index c5d0837..9c54d55 100644 --- a/src/lib/api/providers.ts +++ b/src/lib/api/providers.ts @@ -15,31 +15,31 @@ export interface ProviderSwitchEvent { export const providersApi = { async getAll(appType: AppType): Promise> { - return await invoke("get_providers", { app_type: appType }); + return await invoke("get_providers", { app: appType }); }, async getCurrent(appType: AppType): Promise { - return await invoke("get_current_provider", { app_type: appType }); + return await invoke("get_current_provider", { app: appType }); }, async add(provider: Provider, appType: AppType): Promise { - return await invoke("add_provider", { provider, app_type: appType }); + return await invoke("add_provider", { provider, app: appType }); }, async update(provider: Provider, appType: AppType): Promise { - return await invoke("update_provider", { provider, app_type: appType }); + return await invoke("update_provider", { provider, app: appType }); }, async delete(id: string, appType: AppType): Promise { - return await invoke("delete_provider", { id, app_type: appType }); + return await invoke("delete_provider", { id, app: appType }); }, async switch(id: string, appType: AppType): Promise { - return await invoke("switch_provider", { id, app_type: appType }); + return await invoke("switch_provider", { id, app: appType }); }, async importDefault(appType: AppType): Promise { - return await invoke("import_default_config", { app_type: appType }); + return await invoke("import_default_config", { app: appType }); }, async updateTrayMenu(): Promise { @@ -50,10 +50,7 @@ export const providersApi = { updates: ProviderSortUpdate[], appType: AppType, ): Promise { - return await invoke("update_providers_sort_order", { - updates, - app_type: appType, - }); + return await invoke("update_providers_sort_order", { updates, app: appType }); }, async onSwitched(