Files
cc-switch/src-tauri/src/commands/provider.rs
Jason 590be4e136 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)
2025-10-29 20:33:30 +08:00

259 lines
8.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::collections::HashMap;
use tauri::State;
use crate::app_config::AppType;
use crate::error::AppError;
use crate::provider::Provider;
use crate::services::{EndpointLatency, ProviderService, ProviderSortUpdate, SpeedtestService};
use crate::store::AppState;
fn missing_param(param: &str) -> String {
format!("缺少 {} 参数 (Missing {} parameter)", param, param)
}
fn resolve_app_type(app_type: Option<AppType>, app: Option<String>) -> Result<AppType, String> {
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<AppType>,
app: Option<String>,
) -> Result<HashMap<String, Provider>, String> {
let app_type = resolve_app_type(app_type, app)?;
ProviderService::list(state.inner(), app_type).map_err(|e| e.to_string())
}
/// 获取当前供应商ID
#[tauri::command]
pub fn get_current_provider(
state: State<'_, AppState>,
app_type: Option<AppType>,
app: Option<String>,
) -> Result<String, String> {
let app_type = resolve_app_type(app_type, app)?;
ProviderService::current(state.inner(), app_type).map_err(|e| e.to_string())
}
/// 添加供应商
#[tauri::command]
pub fn add_provider(
state: State<'_, AppState>,
app_type: Option<AppType>,
app: Option<String>,
provider: Provider,
) -> Result<bool, String> {
let app_type = resolve_app_type(app_type, app)?;
ProviderService::add(state.inner(), app_type, provider).map_err(|e| e.to_string())
}
/// 更新供应商
#[tauri::command]
pub fn update_provider(
state: State<'_, AppState>,
app_type: Option<AppType>,
app: Option<String>,
provider: Provider,
) -> Result<bool, String> {
let app_type = resolve_app_type(app_type, app)?;
ProviderService::update(state.inner(), app_type, provider).map_err(|e| e.to_string())
}
/// 删除供应商
#[tauri::command]
pub fn delete_provider(
state: State<'_, AppState>,
app_type: Option<AppType>,
app: Option<String>,
id: String,
) -> Result<bool, String> {
let app_type = resolve_app_type(app_type, app)?;
ProviderService::delete(state.inner(), app_type, &id)
.map(|_| true)
.map_err(|e| e.to_string())
}
/// 切换供应商
fn switch_provider_internal(state: &AppState, app_type: AppType, id: &str) -> Result<(), AppError> {
ProviderService::switch(state, app_type, id)
}
#[cfg_attr(not(feature = "test-hooks"), doc(hidden))]
pub fn switch_provider_test_hook(
state: &AppState,
app_type: AppType,
id: &str,
) -> Result<(), AppError> {
switch_provider_internal(state, app_type, id)
}
#[tauri::command]
pub fn switch_provider(
state: State<'_, AppState>,
app_type: Option<AppType>,
app: Option<String>,
id: String,
) -> Result<bool, String> {
let app_type = resolve_app_type(app_type, app)?;
switch_provider_internal(&state, app_type, &id)
.map(|_| true)
.map_err(|e| e.to_string())
}
fn import_default_config_internal(state: &AppState, app_type: AppType) -> Result<(), AppError> {
ProviderService::import_default_config(state, app_type)
}
#[cfg_attr(not(feature = "test-hooks"), doc(hidden))]
pub fn import_default_config_test_hook(
state: &AppState,
app_type: AppType,
) -> Result<(), AppError> {
import_default_config_internal(state, app_type)
}
/// 导入当前配置为默认供应商
#[tauri::command]
pub fn import_default_config(
state: State<'_, AppState>,
app_type: Option<AppType>,
app: Option<String>,
) -> Result<bool, String> {
let app_type = resolve_app_type(app_type, app)?;
import_default_config_internal(&state, app_type)
.map(|_| true)
.map_err(Into::into)
}
/// 查询供应商用量
#[tauri::command]
pub async fn query_provider_usage(
state: State<'_, AppState>,
provider_id: Option<String>,
app_type: Option<AppType>,
app: Option<String>,
) -> Result<crate::provider::UsageResult, String> {
let provider_id = provider_id.ok_or_else(|| missing_param("providerId"))?;
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())
}
/// 读取当前生效的配置内容
#[tauri::command]
pub fn read_live_provider_settings(app_type: Option<AppType>) -> Result<serde_json::Value, String> {
let app_type = app_type.unwrap_or(AppType::Claude);
ProviderService::read_live_settings(app_type).map_err(|e| e.to_string())
}
/// 测试第三方/自定义供应商端点的网络延迟
#[tauri::command]
pub async fn test_api_endpoints(
urls: Vec<String>,
timeout_secs: Option<u64>,
) -> Result<Vec<EndpointLatency>, String> {
SpeedtestService::test_endpoints(urls, timeout_secs)
.await
.map_err(|e| e.to_string())
}
/// 获取自定义端点列表
#[tauri::command]
pub fn get_custom_endpoints(
state: State<'_, AppState>,
app_type: Option<AppType>,
app: Option<String>,
provider_id: Option<String>,
) -> Result<Vec<crate::settings::CustomEndpoint>, String> {
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())
}
/// 添加自定义端点
#[tauri::command]
pub fn add_custom_endpoint(
state: State<'_, AppState>,
app_type: Option<AppType>,
app: Option<String>,
provider_id: Option<String>,
url: String,
) -> Result<(), String> {
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())
}
/// 删除自定义端点
#[tauri::command]
pub fn remove_custom_endpoint(
state: State<'_, AppState>,
app_type: Option<AppType>,
app: Option<String>,
provider_id: Option<String>,
url: String,
) -> Result<(), String> {
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())
}
/// 更新端点最后使用时间
#[tauri::command]
pub fn update_endpoint_last_used(
state: State<'_, AppState>,
app_type: Option<AppType>,
app: Option<String>,
provider_id: Option<String>,
url: String,
) -> Result<(), String> {
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())
}
/// 更新多个供应商的排序
#[tauri::command]
pub fn update_providers_sort_order(
state: State<'_, AppState>,
app_type: Option<AppType>,
app: Option<String>,
updates: Vec<ProviderSortUpdate>,
) -> Result<bool, String> {
let app_type = resolve_app_type(app_type, app)?;
ProviderService::update_sort_order(state.inner(), app_type, updates).map_err(|e| e.to_string())
}