From 931ef7d3ddbf928ddba6835d48d2fa9b9dab8554 Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 30 Oct 2025 11:35:14 +0800 Subject: [PATCH] refactor(api): simplify app type parameter handling to single required parameter Replace the previous dual-parameter approach (app_type/app/appType) with a single required `app: String` parameter across all Tauri commands. This change: - Introduces unified `parse_app()` helper replacing complex `resolve_app_type()` logic - Updates all backend commands in config, mcp, and provider modules - Aligns frontend API calls to use consistent `app` parameter naming - Simplifies MSW test handlers by removing optional parameter handling This improves API clarity and reduces parameter ambiguity while maintaining backward compatibility through error handling. --- src-tauri/src/commands/config.rs | 48 ++++---------- src-tauri/src/commands/mcp.rs | 24 ++++--- src-tauri/src/commands/provider.rs | 101 ++++++++++------------------- src-tauri/src/lib.rs | 7 +- src/lib/api/settings.ts | 4 +- src/lib/api/usage.ts | 2 +- src/lib/api/vscode.ts | 10 +-- tests/msw/handlers.ts | 62 ++++++------------ 8 files changed, 92 insertions(+), 166 deletions(-) diff --git a/src-tauri/src/commands/config.rs b/src-tauri/src/commands/config.rs index 135c886..b715370 100644 --- a/src-tauri/src/commands/config.rs +++ b/src-tauri/src/commands/config.rs @@ -15,18 +15,17 @@ pub async fn get_claude_config_status() -> Result { } /// 获取应用配置状态 -#[tauri::command] -pub async fn get_config_status( - app_type: Option, - app: Option, - appType: Option, -) -> Result { - let app = app_type - .or_else(|| app.as_deref().map(|s| s.into())) - .or_else(|| appType.as_deref().map(|s| s.into())) - .unwrap_or(AppType::Claude); +fn parse_app(app: String) -> Result { + match app.to_lowercase().as_str() { + "claude" => Ok(AppType::Claude), + "codex" => Ok(AppType::Codex), + other => Err(format!("unsupported app: {}", other)), + } +} - match app { +#[tauri::command] +pub async fn get_config_status(app: String) -> Result { + match parse_app(app)? { AppType::Claude => Ok(config::get_claude_config_status()), AppType::Codex => { let auth_path = codex_config::get_codex_auth_path(); @@ -48,17 +47,8 @@ pub async fn get_claude_code_config_path() -> Result { /// 获取当前生效的配置目录 #[tauri::command] -pub async fn get_config_dir( - app_type: Option, - app: Option, - appType: Option, -) -> Result { - let app = app_type - .or_else(|| app.as_deref().map(|s| s.into())) - .or_else(|| appType.as_deref().map(|s| s.into())) - .unwrap_or(AppType::Claude); - - let dir = match app { +pub async fn get_config_dir(app: String) -> Result { + let dir = match parse_app(app)? { AppType::Claude => config::get_claude_config_dir(), AppType::Codex => codex_config::get_codex_config_dir(), }; @@ -68,18 +58,8 @@ pub async fn get_config_dir( /// 打开配置文件夹 #[tauri::command] -pub async fn open_config_folder( - handle: AppHandle, - app_type: Option, - app: Option, - appType: Option, -) -> Result { - let app_type = app_type - .or_else(|| app.as_deref().map(|s| s.into())) - .or_else(|| appType.as_deref().map(|s| s.into())) - .unwrap_or(AppType::Claude); - - let config_dir = match app_type { +pub async fn open_config_folder(handle: AppHandle, app: String) -> Result { + let config_dir = match parse_app(app)? { AppType::Claude => config::get_claude_config_dir(), AppType::Codex => codex_config::get_codex_config_dir(), }; diff --git a/src-tauri/src/commands/mcp.rs b/src-tauri/src/commands/mcp.rs index a52d8da..1ddaa57 100644 --- a/src-tauri/src/commands/mcp.rs +++ b/src-tauri/src/commands/mcp.rs @@ -47,15 +47,23 @@ pub struct McpConfigResponse { } /// 获取 MCP 配置(来自 ~/.cc-switch/config.json) +fn parse_app(app: String) -> Result { + match app.to_lowercase().as_str() { + "claude" => Ok(AppType::Claude), + "codex" => Ok(AppType::Codex), + other => Err(format!("unsupported app: {}", other)), + } +} + #[tauri::command] pub async fn get_mcp_config( state: State<'_, AppState>, - app: Option, + app: String, ) -> Result { let config_path = crate::config::get_app_config_path() .to_string_lossy() .to_string(); - let app_ty = AppType::from(app.as_deref().unwrap_or("claude")); + let app_ty = parse_app(app)?; let servers = McpService::get_servers(&state, app_ty).map_err(|e| e.to_string())?; Ok(McpConfigResponse { config_path, @@ -67,12 +75,12 @@ pub async fn get_mcp_config( #[tauri::command] pub async fn upsert_mcp_server_in_config( state: State<'_, AppState>, - app: Option, + app: String, id: String, spec: serde_json::Value, sync_other_side: Option, ) -> Result { - let app_ty = AppType::from(app.as_deref().unwrap_or("claude")); + let app_ty = parse_app(app)?; McpService::upsert_server(&state, app_ty, &id, spec, sync_other_side.unwrap_or(false)) .map_err(|e| e.to_string()) } @@ -81,10 +89,10 @@ pub async fn upsert_mcp_server_in_config( #[tauri::command] pub async fn delete_mcp_server_in_config( state: State<'_, AppState>, - app: Option, + app: String, id: String, ) -> Result { - let app_ty = AppType::from(app.as_deref().unwrap_or("claude")); + let app_ty = parse_app(app)?; McpService::delete_server(&state, app_ty, &id).map_err(|e| e.to_string()) } @@ -92,11 +100,11 @@ pub async fn delete_mcp_server_in_config( #[tauri::command] pub async fn set_mcp_enabled( state: State<'_, AppState>, - app: Option, + app: String, id: String, enabled: bool, ) -> Result { - let app_ty = AppType::from(app.as_deref().unwrap_or("claude")); + let app_ty = parse_app(app)?; McpService::set_enabled(&state, app_ty, &id, enabled).map_err(|e| e.to_string()) } diff --git a/src-tauri/src/commands/provider.rs b/src-tauri/src/commands/provider.rs index 0819075..a485b41 100644 --- a/src-tauri/src/commands/provider.rs +++ b/src-tauri/src/commands/provider.rs @@ -11,32 +11,11 @@ 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")), +fn parse_app(app: String) -> Result { + match app.to_lowercase().as_str() { + "claude" => Ok(AppType::Claude), + "codex" => Ok(AppType::Codex), + other => Err(format!("unsupported app: {}", other)), } } @@ -44,10 +23,9 @@ fn resolve_app_type(app_type: Option, app: Option) -> Result, - app_type: Option, - app: Option, + app: String, ) -> Result, String> { - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(app)?; ProviderService::list(state.inner(), app_type).map_err(|e| e.to_string()) } @@ -55,10 +33,9 @@ pub fn get_providers( #[tauri::command] pub fn get_current_provider( state: State<'_, AppState>, - app_type: Option, - app: Option, + app: String, ) -> Result { - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(app)?; ProviderService::current(state.inner(), app_type).map_err(|e| e.to_string()) } @@ -66,11 +43,10 @@ pub fn get_current_provider( #[tauri::command] pub fn add_provider( state: State<'_, AppState>, - app_type: Option, - app: Option, + app: String, provider: Provider, ) -> Result { - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(app)?; ProviderService::add(state.inner(), app_type, provider).map_err(|e| e.to_string()) } @@ -78,11 +54,10 @@ pub fn add_provider( #[tauri::command] pub fn update_provider( state: State<'_, AppState>, - app_type: Option, - app: Option, + app: String, provider: Provider, ) -> Result { - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(app)?; ProviderService::update(state.inner(), app_type, provider).map_err(|e| e.to_string()) } @@ -90,11 +65,10 @@ pub fn update_provider( #[tauri::command] pub fn delete_provider( state: State<'_, AppState>, - app_type: Option, - app: Option, + app: String, id: String, ) -> Result { - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(app)?; ProviderService::delete(state.inner(), app_type, &id) .map(|_| true) .map_err(|e| e.to_string()) @@ -117,11 +91,10 @@ pub fn switch_provider_test_hook( #[tauri::command] pub fn switch_provider( state: State<'_, AppState>, - app_type: Option, - app: Option, + app: String, id: String, ) -> Result { - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(app)?; switch_provider_internal(&state, app_type, &id) .map(|_| true) .map_err(|e| e.to_string()) @@ -143,10 +116,9 @@ pub fn import_default_config_test_hook( #[tauri::command] pub fn import_default_config( state: State<'_, AppState>, - app_type: Option, - app: Option, + app: String, ) -> Result { - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(app)?; import_default_config_internal(&state, app_type) .map(|_| true) .map_err(Into::into) @@ -157,11 +129,10 @@ pub fn import_default_config( pub async fn query_provider_usage( state: State<'_, AppState>, provider_id: Option, - app_type: Option, - app: Option, + app: String, ) -> Result { let provider_id = provider_id.ok_or_else(|| missing_param("providerId"))?; - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(app)?; ProviderService::query_usage(state.inner(), app_type, &provider_id) .await .map_err(|e| e.to_string()) @@ -169,9 +140,8 @@ pub async fn query_provider_usage( /// 读取当前生效的配置内容 #[tauri::command] -pub fn read_live_provider_settings(app_type: Option) -> Result { - let app_type = app_type.unwrap_or(AppType::Claude); - +pub fn read_live_provider_settings(app: String) -> Result { + let app_type = parse_app(app)?; ProviderService::read_live_settings(app_type).map_err(|e| e.to_string()) } @@ -190,11 +160,10 @@ pub async fn test_api_endpoints( #[tauri::command] pub fn get_custom_endpoints( state: State<'_, AppState>, - app_type: Option, - app: Option, + app: String, provider_id: Option, ) -> Result, String> { - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(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()) @@ -204,12 +173,11 @@ pub fn get_custom_endpoints( #[tauri::command] pub fn add_custom_endpoint( state: State<'_, AppState>, - app_type: Option, - app: Option, + app: String, provider_id: Option, url: String, ) -> Result<(), String> { - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(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()) @@ -219,12 +187,11 @@ pub fn add_custom_endpoint( #[tauri::command] pub fn remove_custom_endpoint( state: State<'_, AppState>, - app_type: Option, - app: Option, + app: String, provider_id: Option, url: String, ) -> Result<(), String> { - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(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()) @@ -234,12 +201,11 @@ pub fn remove_custom_endpoint( #[tauri::command] pub fn update_endpoint_last_used( state: State<'_, AppState>, - app_type: Option, - app: Option, + app: String, provider_id: Option, url: String, ) -> Result<(), String> { - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(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()) @@ -249,10 +215,9 @@ pub fn update_endpoint_last_used( #[tauri::command] pub fn update_providers_sort_order( state: State<'_, AppState>, - app_type: Option, - app: Option, + app: String, updates: Vec, ) -> Result { - let app_type = resolve_app_type(app_type, app)?; + let app_type = parse_app(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 443fd10..9999b90 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -267,12 +267,7 @@ 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), - Some(app_type_str.clone()), - provider_id, - ) + crate::commands::switch_provider(app_state.clone(), app_type_str.clone(), provider_id) .map_err(AppError::Message)?; // 切换成功后重新创建托盘菜单 diff --git a/src/lib/api/settings.ts b/src/lib/api/settings.ts index ec7cfb7..bb09019 100644 --- a/src/lib/api/settings.ts +++ b/src/lib/api/settings.ts @@ -31,11 +31,11 @@ export const settingsApi = { }, async getConfigDir(appType: AppType): Promise { - return await invoke("get_config_dir", { app_type: appType }); + return await invoke("get_config_dir", { app: appType }); }, async openConfigFolder(appType: AppType): Promise { - await invoke("open_config_folder", { app_type: appType }); + await invoke("open_config_folder", { app: appType }); }, async selectConfigDirectory(defaultPath?: string): Promise { diff --git a/src/lib/api/usage.ts b/src/lib/api/usage.ts index fa4ef81..cbc0682 100644 --- a/src/lib/api/usage.ts +++ b/src/lib/api/usage.ts @@ -6,7 +6,7 @@ export const usageApi = { async query(providerId: string, appType: AppType): Promise { return await invoke("query_provider_usage", { provider_id: providerId, - app_type: appType, + app: appType, }); }, }; diff --git a/src/lib/api/vscode.ts b/src/lib/api/vscode.ts index c2fe05e..1ddea7c 100644 --- a/src/lib/api/vscode.ts +++ b/src/lib/api/vscode.ts @@ -11,7 +11,7 @@ export interface EndpointLatencyResult { export const vscodeApi = { async getLiveProviderSettings(appType: AppType) { - return await invoke("read_live_provider_settings", { app_type: appType }); + return await invoke("read_live_provider_settings", { app: appType }); }, async testApiEndpoints( @@ -29,7 +29,7 @@ export const vscodeApi = { providerId: string, ): Promise { return await invoke("get_custom_endpoints", { - app_type: appType, + app: appType, provider_id: providerId, }); }, @@ -40,7 +40,7 @@ export const vscodeApi = { url: string, ): Promise { await invoke("add_custom_endpoint", { - app_type: appType, + app: appType, provider_id: providerId, url, }); @@ -52,7 +52,7 @@ export const vscodeApi = { url: string, ): Promise { await invoke("remove_custom_endpoint", { - app_type: appType, + app: appType, provider_id: providerId, url, }); @@ -64,7 +64,7 @@ export const vscodeApi = { url: string, ): Promise { await invoke("update_endpoint_last_used", { - app_type: appType, + app: appType, provider_id: providerId, url, }); diff --git a/tests/msw/handlers.ts b/tests/msw/handlers.ts index 044e75d..b802d10 100644 --- a/tests/msw/handlers.ts +++ b/tests/msw/handlers.ts @@ -37,81 +37,59 @@ const success = (payload: T) => HttpResponse.json(payload as any); export const handlers = [ http.post(`${TAURI_ENDPOINT}/get_providers`, async ({ request }) => { - const { app_type, app } = await withJson<{ app_type?: AppType; app?: AppType }>( - request, - ); - const appType = app ?? app_type!; - return success(getProviders(appType)); + const { app } = await withJson<{ app: AppType }>(request); + return success(getProviders(app)); }), http.post(`${TAURI_ENDPOINT}/get_current_provider`, async ({ request }) => { - const { app_type, app } = await withJson<{ app_type?: AppType; app?: AppType }>( - request, - ); - const appType = app ?? app_type!; - return success(getCurrentProviderId(appType)); + const { app } = await withJson<{ app: AppType }>(request); + return success(getCurrentProviderId(app)); }), http.post(`${TAURI_ENDPOINT}/update_providers_sort_order`, async ({ request }) => { - const { updates = [], app_type, app } = await withJson<{ + const { updates = [], app } = await withJson<{ updates: { id: string; sortIndex: number }[]; - app_type?: AppType; - app?: AppType; + app: AppType; }>(request); - const appType = app ?? app_type!; - updateSortOrder(appType, updates); + updateSortOrder(app, updates); return success(true); }), http.post(`${TAURI_ENDPOINT}/update_tray_menu`, () => success(true)), http.post(`${TAURI_ENDPOINT}/switch_provider`, async ({ request }) => { - const { id, app_type, app } = await withJson<{ - id: string; - app_type?: AppType; - app?: AppType; - }>(request); - const appType = app ?? app_type!; - const providers = listProviders(appType); + const { id, app } = await withJson<{ id: string; app: AppType }>(request); + const providers = listProviders(app); if (!providers[id]) { return HttpResponse.json(false, { status: 404 }); } - setCurrentProviderId(appType, id); + setCurrentProviderId(app, id); return success(true); }), http.post(`${TAURI_ENDPOINT}/add_provider`, async ({ request }) => { - const { provider, app_type, app } = await withJson<{ + const { provider, app } = await withJson<{ provider: Provider & { id?: string }; - app_type?: AppType; - app?: AppType; + app: AppType; }>(request); const newId = provider.id ?? `mock-${Date.now()}`; - const appType = app ?? app_type!; - addProvider(appType, { ...provider, id: newId }); + addProvider(app, { ...provider, id: newId }); return success(true); }), http.post(`${TAURI_ENDPOINT}/update_provider`, async ({ request }) => { - const { provider, app_type, app } = await withJson<{ + const { provider, app } = await withJson<{ provider: Provider; - app_type?: AppType; - app?: AppType; + app: AppType; }>(request); - const appType = app ?? app_type!; - updateProvider(appType, provider); + updateProvider(app, provider); return success(true); }), http.post(`${TAURI_ENDPOINT}/delete_provider`, async ({ request }) => { - const { id, app_type, app } = await withJson<{ - id: string; - app_type?: AppType; - app?: AppType; - }>(request); - const appType = app ?? app_type!; - deleteProvider(appType, id); + const { id, app } = await withJson<{ id: string; app: AppType }>(request); + deleteProvider(app, id); return success(true); }), @@ -184,8 +162,8 @@ export const handlers = [ }), http.post(`${TAURI_ENDPOINT}/get_config_dir`, async ({ request }) => { - const { app_type } = await withJson<{ app_type: AppType }>(request); - return success(app_type === "claude" ? "/default/claude" : "/default/codex"); + const { app } = await withJson<{ app: AppType }>(request); + return success(app === "claude" ? "/default/claude" : "/default/codex"); }), http.post(`${TAURI_ENDPOINT}/is_portable_mode`, () => success(false)),