use serde::Serialize;
use crate::kind::Protocol;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, thiserror::Error)]
#[serde(tag = "code", rename_all = "snake_case")]
#[non_exhaustive]
pub enum VerifyError {
#[error("密钥无效或已过期:{detail}")]
AuthFailed {
detail: String,
},
#[error("端点不存在:{requested_url}")]
NotFound {
requested_url: String,
suggested_url: Option<String>,
},
#[error("无法连接到端点{}", if *.proxy_hint { "(国内访问该站点可能需要代理)" } else { "" })]
Unreachable {
proxy_hint: bool,
},
#[error("端点不认识该模型,它提供了 {} 个可选模型", available.len())]
ModelNotFound {
available: Vec<String>,
},
#[error("密钥要求 {expect:?} 协议,当前配置不匹配")]
ProtocolMismatch {
expect: Protocol,
},
#[error("缺少该服务商要求的配置项:{key}")]
MissingExtraField {
key: String,
},
#[error("端点返回了无法解析的内容:{detail}")]
Malformed {
detail: String,
},
}
impl VerifyError {
pub fn is_actionable(&self) -> bool {
!matches!(self, VerifyError::Unreachable { .. })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serializes_with_code_tag() {
let e = VerifyError::NotFound {
requested_url: "https://api.deepseek.com/chat/completions".into(),
suggested_url: Some("https://api.deepseek.com/v1".into()),
};
let j = serde_json::to_string(&e).unwrap();
assert!(
j.contains(r#""code":"not_found""#),
"前端要按 code 分支:{j}"
);
assert!(j.contains("suggested_url"), "一键修正靠这个字段:{j}");
}
#[test]
fn unreachable_is_not_actionable() {
assert!(!VerifyError::Unreachable { proxy_hint: true }.is_actionable());
assert!(VerifyError::AuthFailed { detail: "x".into() }.is_actionable());
}
}