Pulse/frontend-modern/src/api/responseUtils.ts
rcourtman 62cff03210 Remove 103 unused frontend exports
Dead-code sweep. Named exports flagged unused by ts-prune and confirmed
unreferenced by adversarial cross-file verification, removed via a TypeScript
compiler-API codemod (whole-declaration removal by name). tsc --noEmit over the
full project including all test files, and npm run lint (eslint + theme +
canonical-platform audits), both pass.

54 candidates deferred for safety: 1 false positive caught by the type-check
(WizardState is imported by SetupCompletionPanel.tsx), ~20 files whose export
removal orphans secondary imports/helpers (needs a follow-up cascading pass),
and 2 multi-declarator statements.
2026-06-03 12:37:38 +01:00

436 lines
11 KiB
TypeScript

type APIErrorPayload = {
error?: unknown;
message?: unknown;
};
type APIErrorLike = {
status?: unknown;
code?: unknown;
detail?: unknown;
details?: unknown;
};
type APIResponseLike = {
status?: unknown;
};
type APIRecordLike = Record<string, unknown>;
function trimmedOptionalString(value: unknown): string | null {
if (typeof value !== 'string') {
return null;
}
const normalized = value.trim();
return normalized ? normalized : null;
}
function extractErrorMessage(payload: APIErrorPayload): string | null {
if (typeof payload.error === 'string' && payload.error.trim()) {
return payload.error.trim();
}
if (payload.error && typeof payload.error === 'object') {
const nestedError = payload.error as { message?: unknown };
if (typeof nestedError.message === 'string' && nestedError.message.trim()) {
return nestedError.message.trim();
}
}
if (typeof payload.message === 'string' && payload.message.trim()) {
return payload.message.trim();
}
return null;
}
export async function readAPIErrorMessage(response: Response, fallback: string): Promise<string> {
try {
const text = await response.text();
const trimmed = text.trim();
if (!trimmed) {
return fallback;
}
try {
const parsed = JSON.parse(trimmed) as APIErrorPayload;
return extractErrorMessage(parsed) ?? trimmed;
} catch {
return trimmed;
}
} catch {
return fallback;
}
}
export async function assertAPIResponseOK(response: Response, fallback: string): Promise<void> {
if (response.ok) {
return;
}
throw new Error(await readAPIErrorMessage(response, fallback));
}
function responseHasAllowedStatus(
response: APIResponseLike | null | undefined,
allowedStatuses: number | number[],
): boolean {
const statuses = Array.isArray(allowedStatuses) ? allowedStatuses : [allowedStatuses];
return statuses.some((status) => isAPIResponseStatus(response, status));
}
export async function assertAPIResponseOKOrAllowedStatus(
response: Response,
allowedStatuses: number | number[],
fallback: string,
): Promise<void> {
if (responseHasAllowedStatus(response, allowedStatuses)) {
return;
}
await assertAPIResponseOK(response, fallback);
}
export async function assertAPIResponseOKOrThrowStatus(
response: Response,
throwStatus: number,
throwMessage: string,
fallback: string,
): Promise<void> {
if (isAPIResponseStatus(response, throwStatus)) {
throw new Error(throwMessage);
}
await assertAPIResponseOK(response, fallback);
}
export async function parseRequiredAPIResponse<T>(
response: Response,
requestErrorMessage: string,
parseErrorMessage: string,
): Promise<T> {
await assertAPIResponseOK(response, requestErrorMessage);
return parseRequiredJSON(response, parseErrorMessage);
}
export async function parseOptionalAPIResponse<T>(
response: Response,
emptyValue: T,
requestErrorMessage: string,
parseErrorMessage: string,
): Promise<T> {
await assertAPIResponseOK(response, requestErrorMessage);
return parseOptionalJSON(response, emptyValue, parseErrorMessage);
}
export async function parseOptionalSuccessAPIResponse<
T extends {
success?: boolean;
},
>(
response: Response,
requestErrorMessage: string,
parseErrorMessage: string,
): Promise<T & { success: boolean }> {
return parseOptionalAPIResponse<T & { success: boolean }>(
response,
{ success: true } as T & { success: boolean },
requestErrorMessage,
parseErrorMessage,
);
}
export async function parseOptionalAPIResponseOrAllowedStatus<T>(
response: Response,
allowedStatuses: number | number[],
emptyValue: T,
requestErrorMessage: string,
parseErrorMessage: string,
): Promise<T> {
if (responseHasAllowedStatus(response, allowedStatuses)) {
return emptyValue;
}
return parseOptionalAPIResponse(response, emptyValue, requestErrorMessage, parseErrorMessage);
}
export async function parseRequiredAPIResponseOrNull<T>(
response: Response,
nullStatus: number,
requestErrorMessage: string,
parseErrorMessage: string,
): Promise<T | null> {
if (isAPIResponseStatus(response, nullStatus)) {
return null;
}
return parseRequiredAPIResponse(response, requestErrorMessage, parseErrorMessage);
}
export async function parseOptionalAPIResponseOrNull<T>(
response: Response,
nullStatus: number,
requestErrorMessage: string,
parseErrorMessage: string,
): Promise<T | null> {
if (isAPIResponseStatus(response, nullStatus)) {
return null;
}
return parseOptionalAPIResponse(response, null, requestErrorMessage, parseErrorMessage);
}
export function apiErrorStatus(error: unknown): number | null {
if (!error || typeof error !== 'object') {
return null;
}
const status = (error as APIErrorLike).status;
if (typeof status !== 'number' || !Number.isInteger(status) || status < 100 || status > 599) {
return null;
}
return status;
}
export function apiErrorCode(error: unknown): string | null {
if (!error || typeof error !== 'object') {
return null;
}
return trimmedOptionalString((error as APIErrorLike).code);
}
export function apiErrorDetails(error: unknown): Record<string, string> | null {
if (!error || typeof error !== 'object') {
return null;
}
const rawDetails = (error as APIErrorLike).details;
if (!rawDetails || typeof rawDetails !== 'object') {
return null;
}
const normalizedEntries = Object.entries(rawDetails as APIRecordLike)
.map(([key, value]) => {
const normalizedKey = trimmedOptionalString(key);
const normalizedValue = trimmedOptionalString(value);
return normalizedKey && normalizedValue ? [normalizedKey, normalizedValue] : null;
})
.filter((entry): entry is [string, string] => entry !== null);
if (normalizedEntries.length === 0) {
return null;
}
return Object.fromEntries(normalizedEntries);
}
export function apiErrorDetailField(error: unknown, field: string): string | null {
const details = apiErrorDetails(error);
if (!details) {
return null;
}
return details[field] ?? null;
}
export function isAPIErrorStatus(error: unknown, expectedStatus: number): boolean {
return apiErrorStatus(error) === expectedStatus;
}
export async function withAPIErrorStatusFallback<T>(
request: Promise<T>,
fallbackStatus: number,
fallbackValue: T,
): Promise<T> {
try {
return await request;
} catch (error) {
if (isAPIErrorStatus(error, fallbackStatus)) {
return fallbackValue;
}
throw error;
}
}
export async function withAPIErrorStatusNull<T>(
request: Promise<T>,
nullStatus: number,
): Promise<T | null> {
return withAPIErrorStatusFallback<T | null>(request as Promise<T | null>, nullStatus, null);
}
export function apiResponseStatus(response: APIResponseLike | null | undefined): number | null {
if (!response || typeof response !== 'object') {
return null;
}
const status = response.status;
if (typeof status !== 'number' || !Number.isInteger(status) || status < 100 || status > 599) {
return null;
}
return status;
}
export function isAPIResponseStatus(
response: APIResponseLike | null | undefined,
expectedStatus: number,
): boolean {
return apiResponseStatus(response) === expectedStatus;
}
export function arrayOrEmpty<T>(value: unknown): T[] {
return Array.isArray(value) ? (value as T[]) : [];
}
export function arrayOrUndefined<T>(value: unknown): T[] | undefined {
return Array.isArray(value) ? (value as T[]) : undefined;
}
export function objectArrayFieldOrEmpty<T>(value: unknown, field: string): T[] {
if (!value || typeof value !== 'object') {
return [];
}
return arrayOrEmpty<T>((value as APIRecordLike)[field]);
}
export function trimmedString(value: unknown): string {
return typeof value === 'string' ? value.trim() : value == null ? '' : String(value).trim();
}
export function optionalTrimmedString(value: unknown): string | undefined {
const normalized = trimmedString(value);
return normalized.length > 0 ? normalized : undefined;
}
export function strictString(value: unknown, fallback = ''): string {
return typeof value === 'string' ? value : fallback;
}
export function strictBoolean(value: unknown, fallback = false): boolean {
return typeof value === 'boolean' ? value : fallback;
}
export function finiteNumberOrUndefined(value: unknown): number | undefined {
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
}
export function coerceTimestampMillis(value: unknown, fallback: number): number {
if (typeof value === 'number' && Number.isFinite(value)) {
return value;
}
const normalized = optionalTrimmedString(value);
if (normalized) {
const parsed = Date.parse(normalized);
if (Number.isFinite(parsed)) {
return parsed;
}
}
return fallback;
}
export function stringArray(value: unknown): string[] {
return arrayOrEmpty<unknown>(value).filter((item): item is string => typeof item === 'string');
}
export function stringRecordOrUndefined(value: unknown): Record<string, string> | undefined {
if (!value || typeof value !== 'object') {
return undefined;
}
const entries = Object.entries(value).filter(
(entry): entry is [string, string] => typeof entry[1] === 'string',
);
if (entries.length === 0) {
return undefined;
}
return Object.fromEntries(entries);
}
export function normalizeStructuredAPIError(
payload: unknown,
fallbackStatus: number,
fallbackCode = 'request_failed',
): { code: string; message: string; details?: Record<string, string> } {
const fallbackMessage = `Request failed (${fallbackStatus})`;
if (!payload || typeof payload !== 'object') {
return {
code: fallbackCode,
message: fallbackMessage,
};
}
const record = payload as APIRecordLike;
return {
code: optionalTrimmedString(record.code) ?? fallbackCode,
message: optionalTrimmedString(record.message) ?? fallbackMessage,
details: stringRecordOrUndefined(record.details),
};
}
export function promoteLegacyAlertIdentifier<T extends { alertIdentifier?: string } & object>(
record: T & { alert_identifier?: string },
): T {
const alertIdentifier =
optionalTrimmedString(record.alertIdentifier) ?? optionalTrimmedString(record.alert_identifier);
const { alert_identifier: _alertIdentifier, ...rest } = record;
return alertIdentifier ? ({ ...rest, alertIdentifier } as T) : (rest as T);
}
export function parseJSONTextSafe<T>(text: string): T | null {
if (!text.trim()) {
return null;
}
try {
return JSON.parse(text) as T;
} catch {
return null;
}
}
export async function parseOptionalJSON<T>(
response: Response,
emptyValue: T,
parseErrorMessage: string,
): Promise<T> {
const text = await response.text();
if (!text.trim()) {
return emptyValue;
}
try {
return JSON.parse(text) as T;
} catch {
throw new Error(parseErrorMessage);
}
}
export async function parseRequiredJSON<T>(
response: Response,
parseErrorMessage: string,
): Promise<T> {
const text = await response.text();
if (!text.trim()) {
throw new Error(parseErrorMessage);
}
try {
return JSON.parse(text) as T;
} catch {
throw new Error(parseErrorMessage);
}
}
export async function parseJSONSafe<T>(response: Response): Promise<T | null> {
const text = await response.text();
return parseJSONTextSafe(text);
}