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)
259 lines
8.0 KiB
Rust
259 lines
8.0 KiB
Rust
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())
|
||
}
|