use axum::http::{HeaderValue, StatusCode}; use platform_auth::{AuthPlatformErrorKind, WechatProviderError}; use platform_llm::{LlmError, LlmErrorKind}; use platform_oss::{OssError, OssErrorKind}; use serde_json::json; use crate::http_error::AppError; // API 层统一消费 platform 的稳定错误分类,避免各 route 重复 match 具体 provider 分支。 pub fn map_llm_error(error: LlmError) -> AppError { let message = llm_error_message(&error); let status = match error.kind() { LlmErrorKind::InvalidRequest => StatusCode::BAD_REQUEST, LlmErrorKind::InvalidConfig => StatusCode::SERVICE_UNAVAILABLE, LlmErrorKind::Upstream if matches!( error, LlmError::Upstream { status_code: 429, .. } ) => { StatusCode::TOO_MANY_REQUESTS } LlmErrorKind::Timeout | LlmErrorKind::Connectivity | LlmErrorKind::Upstream | LlmErrorKind::StreamUnavailable | LlmErrorKind::EmptyResponse | LlmErrorKind::Transport | LlmErrorKind::Deserialize => StatusCode::BAD_GATEWAY, }; AppError::from_status(status).with_message(message) } pub fn map_oss_error(error: OssError, provider: &'static str) -> AppError { let status = oss_error_status(error.kind()); AppError::from_status(status).with_details(json!({ "provider": provider, "message": error.to_string(), })) } pub fn map_phone_auth_platform_store_error(message: String) -> AppError { AppError::from_status(StatusCode::INTERNAL_SERVER_ERROR).with_message(message) } pub fn map_wechat_provider_error(error: WechatProviderError) -> AppError { let status = match error.kind() { AuthPlatformErrorKind::Disabled | AuthPlatformErrorKind::MissingCode | AuthPlatformErrorKind::InvalidCallback => StatusCode::BAD_REQUEST, AuthPlatformErrorKind::InvalidConfig => StatusCode::SERVICE_UNAVAILABLE, AuthPlatformErrorKind::RequestFailed | AuthPlatformErrorKind::DeserializeFailed | AuthPlatformErrorKind::MissingProfile | AuthPlatformErrorKind::Upstream => StatusCode::BAD_GATEWAY, AuthPlatformErrorKind::InvalidClaims | AuthPlatformErrorKind::SignFailed | AuthPlatformErrorKind::VerifyFailed | AuthPlatformErrorKind::CookieConfig | AuthPlatformErrorKind::HashFailed | AuthPlatformErrorKind::InvalidVerifyCode => StatusCode::INTERNAL_SERVER_ERROR, }; AppError::from_status(status).with_message(error.to_string()) } pub fn attach_retry_after(error: AppError, retry_after_seconds: u64) -> AppError { match HeaderValue::from_str(&retry_after_seconds.to_string()) { Ok(value) => error.with_header("retry-after", value), Err(_) => error, } } fn oss_error_status(kind: OssErrorKind) -> StatusCode { match kind { OssErrorKind::InvalidConfig | OssErrorKind::InvalidRequest => StatusCode::BAD_REQUEST, OssErrorKind::ObjectNotFound => StatusCode::NOT_FOUND, OssErrorKind::Request | OssErrorKind::SerializePolicy | OssErrorKind::Sign => { StatusCode::BAD_GATEWAY } } } fn llm_error_message(error: &LlmError) -> String { match error { LlmError::InvalidConfig(message) | LlmError::InvalidRequest(message) | LlmError::Transport(message) | LlmError::Deserialize(message) => message.clone(), LlmError::Timeout { .. } | LlmError::Connectivity { .. } | LlmError::Upstream { .. } | LlmError::StreamUnavailable | LlmError::EmptyResponse => error.to_string(), } } #[cfg(test)] mod tests { use super::*; #[test] fn map_oss_error_uses_stable_kind_for_not_found() { let error = map_oss_error( OssError::ObjectNotFound("missing object".to_string()), "oss", ); assert_eq!(error.status_code(), StatusCode::NOT_FOUND); } #[test] fn map_llm_error_preserves_upstream_rate_limit() { let error = map_llm_error(LlmError::Upstream { status_code: 429, message: "too many requests".to_string(), }); assert_eq!(error.status_code(), StatusCode::TOO_MANY_REQUESTS); } #[test] fn map_wechat_provider_error_keeps_provider_boundary() { let error = map_wechat_provider_error(WechatProviderError::MissingCode); assert_eq!(error.status_code(), StatusCode::BAD_REQUEST); assert_eq!(error.message(), "缺少微信授权 code"); } }