优化
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
export type ApiFieldErrors = Record<string, string>
|
||||
|
||||
export function asFieldErrors(value: unknown): ApiFieldErrors | undefined {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined
|
||||
const entries = Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string')
|
||||
return entries.length > 0 ? Object.fromEntries(entries) : undefined
|
||||
}
|
||||
|
||||
export function apiErrorMessage(message: string, fieldErrors?: ApiFieldErrors) {
|
||||
const baseMessage = message.trim() || '请求失败'
|
||||
const entries = Object.entries(fieldErrors ?? {})
|
||||
if (entries.length === 0) return baseMessage
|
||||
if (entries.every(([field, detail]) => baseMessage.includes(field) && baseMessage.includes(detail))) return baseMessage
|
||||
const details = entries.map(([field, detail]) => `${field}: ${detail}`).join(';')
|
||||
return `${baseMessage}:${details}`
|
||||
}
|
||||
+7
-4
@@ -1,7 +1,8 @@
|
||||
import axios, { AxiosError, type AxiosRequestConfig, type InternalAxiosRequestConfig } from 'axios'
|
||||
import { parseApiJsonResponse } from '@chongchong/shared'
|
||||
import { clearSessionTokens, getAccessToken, getRefreshToken, persistTokenPair, type TokenPair } from '@/api/session'
|
||||
import { ApiError, asFieldErrors, isApiEnvelope, type ApiEnvelope } from '@/api/types'
|
||||
import { apiErrorMessage, asFieldErrors } from '@/api/errorDetails'
|
||||
import { ApiError, isApiEnvelope, type ApiEnvelope } from '@/api/types'
|
||||
|
||||
const transport = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
|
||||
@@ -15,7 +16,8 @@ interface RetryableRequest extends InternalAxiosRequestConfig {
|
||||
|
||||
function unwrap<T>(envelope: ApiEnvelope<T>) {
|
||||
if (envelope.code !== '200') {
|
||||
throw new ApiError(envelope.message || '请求失败', envelope.code, undefined, asFieldErrors(envelope.data), envelope.traceId)
|
||||
const fieldErrors = asFieldErrors(envelope.data)
|
||||
throw new ApiError(apiErrorMessage(envelope.message, fieldErrors), envelope.code, undefined, fieldErrors, envelope.traceId)
|
||||
}
|
||||
return envelope.data
|
||||
}
|
||||
@@ -26,11 +28,12 @@ function mapError(error: unknown) {
|
||||
|
||||
const body = error.response?.data
|
||||
if (isApiEnvelope(body)) {
|
||||
const fieldErrors = asFieldErrors(body.data)
|
||||
return new ApiError(
|
||||
body.message || '请求失败',
|
||||
apiErrorMessage(body.message, fieldErrors),
|
||||
body.code,
|
||||
error.response?.status,
|
||||
asFieldErrors(body.data),
|
||||
fieldErrors,
|
||||
typeof body.traceId === 'string' ? body.traceId : null,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { apiErrorMessage, asFieldErrors } from './errorDetails.ts'
|
||||
|
||||
test('adds validation field details to a generic API message', () => {
|
||||
const errors = asFieldErrors({ carrierName: '不能为空', carrierId: '不能为null' })
|
||||
|
||||
assert.equal(
|
||||
apiErrorMessage('Validation failed', errors),
|
||||
'Validation failed:carrierName: 不能为空;carrierId: 不能为null',
|
||||
)
|
||||
})
|
||||
|
||||
test('does not duplicate validation details already included by the server', () => {
|
||||
const errors = { carrierName: '不能为空', carrierId: '不能为null' }
|
||||
const message = 'carrierName: 不能为空;carrierId: 不能为null'
|
||||
|
||||
assert.equal(apiErrorMessage(message, errors), message)
|
||||
})
|
||||
+3
-7
@@ -1,3 +1,5 @@
|
||||
import type { ApiFieldErrors } from '@/api/errorDetails'
|
||||
|
||||
export interface ApiEnvelope<T> {
|
||||
code: string
|
||||
message: string
|
||||
@@ -7,7 +9,7 @@ export interface ApiEnvelope<T> {
|
||||
timestamp: string
|
||||
}
|
||||
|
||||
export type ApiFieldErrors = Record<string, string>
|
||||
export type { ApiFieldErrors } from '@/api/errorDetails'
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
@@ -27,9 +29,3 @@ export function isApiEnvelope(value: unknown): value is ApiEnvelope<unknown> {
|
||||
const record = value as Record<string, unknown>
|
||||
return typeof record.code === 'string' && typeof record.message === 'string' && 'data' in record
|
||||
}
|
||||
|
||||
export function asFieldErrors(value: unknown): ApiFieldErrors | undefined {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined
|
||||
const entries = Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === 'string')
|
||||
return entries.length > 0 ? Object.fromEntries(entries) : undefined
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user