78 lines
2.7 KiB
TypeScript
78 lines
2.7 KiB
TypeScript
import { isApiErrorV1 } from './simulation-contract.ts';
|
|
|
|
export type SimulationApiChoice = { id: string; label: string };
|
|
|
|
function scalar(value: unknown): string {
|
|
return typeof value === 'string' ||
|
|
(typeof value === 'number' && Number.isFinite(value))
|
|
? String(value)
|
|
: '';
|
|
}
|
|
|
|
/** Parse API bodies without leaking JSON parser errors into the public form. */
|
|
export async function readSimulationApiJson(
|
|
response: Response,
|
|
): Promise<unknown> {
|
|
try {
|
|
return await response.json();
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** Prefer field validation details, falling back to the shared API message. */
|
|
export function simulationApiErrorMessage(value: unknown): string | undefined {
|
|
if (!isApiErrorV1(value)) return undefined;
|
|
const validationMessages = Object.values(value.errors ?? {}).flat();
|
|
return validationMessages.length
|
|
? validationMessages.join(' ')
|
|
: value.message;
|
|
}
|
|
|
|
/** Keep snapshot field errors attached to their original form fields. */
|
|
export function simulationApiFieldErrors(
|
|
value: unknown,
|
|
): Record<string, string> {
|
|
if (!isApiErrorV1(value)) return {};
|
|
const fields: Record<string, string> = {};
|
|
for (const [path, messages] of Object.entries(value.errors ?? {})) {
|
|
const key = path
|
|
.replace(/^(economic_snapshot|personal_snapshot)\./, '')
|
|
.split('.')[0];
|
|
if (/^[a-z][a-z0-9_]*$/.test(key) && messages.length)
|
|
fields[key] = messages.join(' ');
|
|
}
|
|
return fields;
|
|
}
|
|
|
|
/** Explain a failed status check without implying the accepted job was lost. */
|
|
export function simulationPollingFailureMessage(value: unknown): string {
|
|
return (
|
|
simulationApiErrorMessage(value) ??
|
|
'O cenário foi recebido, mas não foi possível confirmar o estado do cálculo. Consulte o histórico antes de tentar novamente.'
|
|
);
|
|
}
|
|
|
|
/** Validate and normalize the public reference catalog envelope. */
|
|
export function simulationCatalogChoices(
|
|
value: unknown,
|
|
): SimulationApiChoice[] {
|
|
if (
|
|
!value ||
|
|
typeof value !== 'object' ||
|
|
Array.isArray(value) ||
|
|
!Array.isArray((value as Record<string, unknown>).data)
|
|
)
|
|
throw new Error('O servidor devolveu opções com formato inválido.');
|
|
|
|
return (value as { data: unknown[] }).data.flatMap((row) => {
|
|
if (!row || typeof row !== 'object' || Array.isArray(row)) return [];
|
|
const record = row as Record<string, unknown>;
|
|
const choice = {
|
|
id: scalar(record.id ?? record.value),
|
|
label: scalar(record.label ?? record.name),
|
|
};
|
|
return choice.id && choice.label ? [choice] : [];
|
|
});
|
|
}
|