Files
CBS-Mobile-App/src/services/api/serviceHelpers.ts
T

83 lines
2.1 KiB
TypeScript

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 <T>({
mockData,
request,
delayMs = 320,
}: {
mockData: T | (() => T);
request: () => Promise<T>;
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 <T>(
request: () => Promise<T>,
): Promise<{ data: T } | { error: ApiError }> => {
try {
return { data: await request() };
} catch (error) {
return { error: toApiError(error) };
}
};