Files
Aether/frontend/src/utils/errorParser.ts

196 lines
5.5 KiB
TypeScript
Raw Normal View History

2025-12-10 20:52:44 +08:00
/**
* API
*/
/**
* Pydantic
*/
interface ValidationError {
loc: (string | number)[]
msg: string
type: string
ctx?: Record<string, any>
}
/**
*
*/
const fieldNameMap: Record<string, string> = {
'api_key': 'API 密钥',
'priority': '优先级',
'max_concurrent': '最大并发',
'rate_limit': '速率限制',
'daily_limit': '每日限制',
'monthly_limit': '每月限制',
'allowed_models': '允许的模型',
'note': '备注',
'is_active': '启用状态',
'endpoint_id': 'Endpoint ID',
'base_url': 'API 基础 URL',
'timeout': '超时时间',
'max_retries': '最大重试次数',
'weight': '权重',
'email': '邮箱',
'username': '用户名',
'password': '密码',
'name': '名称',
'display_name': '显示名称',
'description': '描述',
'website': '网站',
'provider_priority': '提供商优先级',
'billing_type': '计费类型',
'monthly_quota_usd': '月度配额',
'quota_reset_day': '配额重置日',
'quota_expires_at': '配额过期时间',
'rpm_limit': 'RPM 限制',
'cache_ttl_minutes': '缓存 TTL',
'max_probe_interval_minutes': '最大探测间隔',
}
/**
*
*/
const errorTypeMap: Record<string, (error: ValidationError) => string> = {
'string_too_short': (error) => {
const minLength = error.ctx?.min_length || 10
return `长度不能少于 ${minLength} 个字符`
},
'string_too_long': (error) => {
const maxLength = error.ctx?.max_length
return `长度不能超过 ${maxLength} 个字符`
},
'value_error.missing': () => '此字段为必填项',
'missing': () => '此字段为必填项',
'type_error.none.not_allowed': () => '此字段不能为空',
'value_error': (error) => error.msg,
'type_error.integer': () => '必须为整数',
'type_error.float': () => '必须为数字',
'value_error.number.not_ge': (error) => {
const limit = error.ctx?.limit_value
return limit !== undefined ? `不能小于 ${limit}` : '数值过小'
},
'value_error.number.not_le': (error) => {
const limit = error.ctx?.limit_value
return limit !== undefined ? `不能大于 ${limit}` : '数值过大'
},
'value_error.number.not_gt': (error) => {
const limit = error.ctx?.limit_value
return limit !== undefined ? `必须大于 ${limit}` : '数值过小'
},
'value_error.number.not_lt': (error) => {
const limit = error.ctx?.limit_value
return limit !== undefined ? `必须小于 ${limit}` : '数值过大'
},
'less_than_equal': (error) => {
const limit = error.ctx?.le
return limit !== undefined ? `不能大于 ${limit}` : '数值过大'
},
'greater_than_equal': (error) => {
const limit = error.ctx?.ge
return limit !== undefined ? `不能小于 ${limit}` : '数值过小'
},
'less_than': (error) => {
const limit = error.ctx?.lt
return limit !== undefined ? `必须小于 ${limit}` : '数值过大'
},
'greater_than': (error) => {
const limit = error.ctx?.gt
return limit !== undefined ? `必须大于 ${limit}` : '数值过小'
},
'value_error.email': () => '邮箱格式不正确',
'value_error.url': () => 'URL 格式不正确',
'type_error.bool': () => '必须为布尔值true/false',
'type_error.list': () => '必须为数组',
'type_error.dict': () => '必须为对象',
}
/**
*
*/
function getFieldName(loc: (string | number)[]): string {
if (!loc || loc.length === 0) return '字段'
const fieldPath = loc.filter(item => item !== 'body').join('.')
const fieldKey = String(loc[loc.length - 1])
return fieldNameMap[fieldKey] || fieldPath || '字段'
}
/**
*
*/
function formatValidationError(error: ValidationError): string {
const fieldName = getFieldName(error.loc)
const errorFormatter = errorTypeMap[error.type]
if (errorFormatter) {
const errorMsg = errorFormatter(error)
return `${fieldName}: ${errorMsg}`
}
// 默认格式
return `${fieldName}: ${error.msg}`
}
/**
* API
* @param err
* @param defaultMessage
* @returns
*/
export function parseApiError(err: any, defaultMessage: string = '操作失败'): string {
if (!err) return defaultMessage
// 处理网络错误
if (!err.response) {
return '无法连接到服务器,请检查网络连接'
}
const detail = err.response?.data?.detail
// 如果没有 detail 字段
if (!detail) {
return err.response?.data?.message || err.message || defaultMessage
}
// 1. 处理 Pydantic 验证错误(数组格式)
if (Array.isArray(detail)) {
const errors = detail
.map((error: ValidationError) => formatValidationError(error))
.join('\n')
return errors || defaultMessage
}
// 2. 处理字符串错误
if (typeof detail === 'string') {
return detail
}
// 3. 处理对象错误
if (typeof detail === 'object') {
// 可能是自定义错误对象
if (detail.message) {
return detail.message
}
// 尝试 JSON 序列化
try {
return JSON.stringify(detail, null, 2)
} catch {
return defaultMessage
}
}
return defaultMessage
}
/**
*
*/
export function parseApiErrorShort(err: any, defaultMessage: string = '操作失败'): string {
const fullError = parseApiError(err, defaultMessage)
// 如果有多行错误,只取第一行
const lines = fullError.split('\n')
return lines[0] || defaultMessage
}