43 lines
1.4 KiB
TypeScript
43 lines
1.4 KiB
TypeScript
import type { ApiFieldErrors } from '@/api/errorDetails'
|
|
|
|
export interface ApiEnvelope<T> {
|
|
code: string
|
|
message: string
|
|
data: T
|
|
requestId: string | null
|
|
traceId: string | null
|
|
timestamp: string
|
|
}
|
|
|
|
export type { ApiFieldErrors } from '@/api/errorDetails'
|
|
|
|
export class ApiError extends Error {
|
|
constructor(
|
|
message: string,
|
|
readonly code = 'NETWORK_ERROR',
|
|
readonly status?: number,
|
|
readonly fieldErrors?: ApiFieldErrors,
|
|
readonly traceId?: string | null,
|
|
readonly requestId?: string | null,
|
|
) {
|
|
super(message)
|
|
this.name = 'ApiError'
|
|
}
|
|
}
|
|
|
|
export function isApiEnvelope(value: unknown): value is ApiEnvelope<unknown> {
|
|
if (!value || typeof value !== 'object') return false
|
|
const record = value as Record<string, unknown>
|
|
return typeof record.code === 'string' && typeof record.message === 'string' && 'data' in record
|
|
}
|
|
|
|
// Security failures have no business data, unlike successful API responses.
|
|
export function isApiFailure(value: unknown): value is Omit<ApiEnvelope<never>, 'data'> {
|
|
if (!value || typeof value !== 'object') return false
|
|
const record = value as Record<string, unknown>
|
|
return typeof record.code === 'string' && record.code !== '200'
|
|
&& typeof record.message === 'string'
|
|
&& (record.requestId == null || typeof record.requestId === 'string')
|
|
&& (record.traceId == null || typeof record.traceId === 'string')
|
|
}
|