mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-24 12:44:18 +08:00
689ca08409
Distinguish between "provider rejects probe" (yellow warning) and "genuinely broken" (red error) in health check results. Backend: add AppError::HttpStatus variant to carry structured HTTP status codes, populate http_status on error results, classify codes into short labels (e.g. "Auth rejected (401)"), and truncate overly long response bodies. Frontend: route 401/403/400/429/5xx to toast.warning with localized hints explaining the error may not indicate actual unusability; route 404/402/connection errors to toast.error. Add i18n keys for all three locales (zh/en/ja). Also deduplicate check_once by reusing build_stream_check_result.
147 lines
3.5 KiB
Rust
147 lines
3.5 KiB
Rust
use std::path::Path;
|
|
use std::sync::PoisonError;
|
|
|
|
use thiserror::Error;
|
|
|
|
#[derive(Debug, Error)]
|
|
pub enum AppError {
|
|
#[error("配置错误: {0}")]
|
|
Config(String),
|
|
#[error("无效输入: {0}")]
|
|
InvalidInput(String),
|
|
#[error("IO 错误: {path}: {source}")]
|
|
Io {
|
|
path: String,
|
|
#[source]
|
|
source: std::io::Error,
|
|
},
|
|
#[error("{context}: {source}")]
|
|
IoContext {
|
|
context: String,
|
|
#[source]
|
|
source: std::io::Error,
|
|
},
|
|
#[error("JSON 解析错误: {path}: {source}")]
|
|
Json {
|
|
path: String,
|
|
#[source]
|
|
source: serde_json::Error,
|
|
},
|
|
#[error("JSON 序列化失败: {source}")]
|
|
JsonSerialize {
|
|
#[source]
|
|
source: serde_json::Error,
|
|
},
|
|
#[error("TOML 解析错误: {path}: {source}")]
|
|
Toml {
|
|
path: String,
|
|
#[source]
|
|
source: toml::de::Error,
|
|
},
|
|
#[error("锁获取失败: {0}")]
|
|
Lock(String),
|
|
#[error("MCP 校验失败: {0}")]
|
|
McpValidation(String),
|
|
#[error("{0}")]
|
|
Message(String),
|
|
#[error("HTTP {status}: {body}")]
|
|
HttpStatus { status: u16, body: String },
|
|
#[error("{zh} ({en})")]
|
|
Localized {
|
|
key: &'static str,
|
|
zh: String,
|
|
en: String,
|
|
},
|
|
#[error("数据库错误: {0}")]
|
|
Database(String),
|
|
#[error("OMO 配置文件不存在")]
|
|
OmoConfigNotFound,
|
|
#[error("所有供应商已熔断,无可用渠道")]
|
|
AllProvidersCircuitOpen,
|
|
#[error("未配置供应商")]
|
|
NoProvidersConfigured,
|
|
}
|
|
|
|
impl AppError {
|
|
pub fn io(path: impl AsRef<Path>, source: std::io::Error) -> Self {
|
|
Self::Io {
|
|
path: path.as_ref().display().to_string(),
|
|
source,
|
|
}
|
|
}
|
|
|
|
pub fn json(path: impl AsRef<Path>, source: serde_json::Error) -> Self {
|
|
Self::Json {
|
|
path: path.as_ref().display().to_string(),
|
|
source,
|
|
}
|
|
}
|
|
|
|
pub fn toml(path: impl AsRef<Path>, source: toml::de::Error) -> Self {
|
|
Self::Toml {
|
|
path: path.as_ref().display().to_string(),
|
|
source,
|
|
}
|
|
}
|
|
|
|
pub fn localized(key: &'static str, zh: impl Into<String>, en: impl Into<String>) -> Self {
|
|
Self::Localized {
|
|
key,
|
|
zh: zh.into(),
|
|
en: en.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> From<PoisonError<T>> for AppError {
|
|
fn from(err: PoisonError<T>) -> Self {
|
|
Self::Lock(err.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<rusqlite::Error> for AppError {
|
|
fn from(err: rusqlite::Error) -> Self {
|
|
Self::Database(err.to_string())
|
|
}
|
|
}
|
|
|
|
impl From<AppError> for String {
|
|
fn from(err: AppError) -> Self {
|
|
err.to_string()
|
|
}
|
|
}
|
|
|
|
impl serde::Serialize for AppError {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.serialize_str(&self.to_string())
|
|
}
|
|
}
|
|
|
|
/// 格式化为 JSON 错误字符串,前端可解析为结构化错误
|
|
pub fn format_skill_error(
|
|
code: &str,
|
|
context: &[(&str, &str)],
|
|
suggestion: Option<&str>,
|
|
) -> String {
|
|
use serde_json::json;
|
|
|
|
let mut ctx_map = serde_json::Map::new();
|
|
for (key, value) in context {
|
|
ctx_map.insert(key.to_string(), json!(value));
|
|
}
|
|
|
|
let error_obj = json!({
|
|
"code": code,
|
|
"context": ctx_map,
|
|
"suggestion": suggestion,
|
|
});
|
|
|
|
serde_json::to_string(&error_obj).unwrap_or_else(|_| {
|
|
// 如果 JSON 序列化失败,返回简单格式
|
|
format!("ERROR:{code}")
|
|
})
|
|
}
|