fix(api): harden response contracts

This commit is contained in:
2026-07-21 16:44:16 +08:00
parent c56ada3708
commit 2275d388a7
11 changed files with 191 additions and 26 deletions

View File

@@ -7,6 +7,7 @@ type RequestOptions = {
method?: string
body?: unknown
token?: string
responseType?: 'json' | 'void'
}
type ErrorEnvelope = {
@@ -54,7 +55,7 @@ export async function apiRequest<T>(path: string, options: RequestOptions = {}):
response = await fetch(`${configuredBaseUrl}${path}`, {
method: options.method ?? 'GET',
headers: {
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
...(options.body !== undefined && !isFormData ? { 'Content-Type': 'application/json' } : {}),
...(options.token ? { Authorization: `Bearer ${options.token}` } : {}),
},
body: requestBody as BodyInit | undefined,
@@ -75,15 +76,28 @@ export async function apiRequest<T>(path: string, options: RequestOptions = {}):
throw new ApiError(response.status, fallbackErrorCode(response.status), fallbackErrorMessage(response.status))
}
if (response.status === 204) {
if (response.status === 204 || options.responseType === 'void') {
return undefined as T
}
const text = await response.text()
if (text.trim() === '') {
return undefined as T
let text: string
try {
text = await response.text()
} catch {
throw invalidResponse(response.status)
}
return JSON.parse(text) as T
if (text.trim() === '') {
throw invalidResponse(response.status)
}
try {
return JSON.parse(text) as T
} catch {
throw invalidResponse(response.status)
}
}
function invalidResponse(status: number) {
return new ApiError(status, 'invalid_response', '服务器返回了无法识别的数据')
}
function isErrorEnvelope(value: unknown): value is ErrorEnvelope {
@@ -119,7 +133,7 @@ function fallbackErrorMessage(status: number) {
return '请求失败,请稍后重试'
}
function normalizeBaseUrl(value: string) {
export function normalizeBaseUrl(value: string) {
const trimmed = value.trim()
return trimmed.endsWith('/') ? trimmed.slice(0, -1) : trimmed
return trimmed.replace(/\/+$/, '')
}