import type { AxiosError } from 'axios'; import { appConfig } from '../../config/appConfig'; import type { ApiError } from '../../types/api'; import { sleep } from '../../utils/async'; import { isCbsApiError } from './cbsErrors'; const isApiError = (value: unknown): value is ApiError => typeof value === 'object' && value !== null && 'status' in value && 'message' in value; export const toApiError = (error: unknown): ApiError => { // A named backend failure. Keep the code and the field map — they are what // the screens branch on and paint onto forms. if (isCbsApiError(error)) { return { status: error.status, message: error.message, code: error.code, fieldErrors: error.fieldErrors, }; } if (isApiError(error)) { return error; } const axiosError = error as AxiosError; return { status: Number(axiosError.response?.status ?? 500), message: axiosError.message || 'Unexpected service error', code: 'UNKNOWN_ERROR', data: axiosError.response?.data, }; }; /** * Fixture-backed resolution for the modules that have no backend behind them * yet. Endpoints the CBS API actually implements must not go through here — * they call `apiClient` directly so a failure surfaces instead of being * papered over with demo data. */ export const resolveWithMock = async ({ mockData, request, delayMs = 320, }: { mockData: T | (() => T); request: () => Promise; delayMs?: number; }): Promise<{ data: T } | { error: ApiError }> => { if (appConfig.features.useMockServer) { await sleep(delayMs); return { data: typeof mockData === 'function' ? (mockData as () => T)() : mockData, }; } try { return { data: await request(), }; } catch (error) { return { error: toApiError(error), }; } }; /** Same contract as `resolveWithMock`, but always live. */ export const resolveLive = async ( request: () => Promise, ): Promise<{ data: T } | { error: ApiError }> => { try { return { data: await request() }; } catch (error) { return { error: toApiError(error) }; } };