50 lines
2.5 KiB
TypeScript
50 lines
2.5 KiB
TypeScript
/**
|
|
* Public error phrases — the ONLY text derived from a caught error that may
|
|
* reach a wire response (`HealthStatus.poller.lastError`, `SyncRun.error`,
|
|
* `SyncObjectProgress.error`; governed contract docs/api/worker.openapi.json).
|
|
*
|
|
* Allowlist keyed on error class / upstream HTTP status, mirroring the 502
|
|
* mapping in server.ts: a raw `err.message` is never copied out, because
|
|
* Arena error bodies can echo request data (and, for the future S/4HANA leg,
|
|
* OData error payloads can echo business data). Anything not on the list gets
|
|
* the unclassified phrase; the full error stays in the worker log, which is
|
|
* where diagnosis belongs (index.ts logs `{ name, status, message }`).
|
|
*
|
|
* `TimeoutError` is what Node's fetch rejects with when the request's
|
|
* `AbortSignal.timeout(...)` fires (measured on this box, Node 24.15.0,
|
|
* 2026-09-14: `name=TimeoutError`, a DOMException).
|
|
*/
|
|
|
|
import { ArenaApiError } from '../arena/session.js';
|
|
import { IngestBufferFullError } from '../poller/arenaDeltaPoll.js';
|
|
|
|
export const UNCLASSIFIED_ERROR_PHRASE = 'Unclassified worker error; see worker logs.';
|
|
export const INGEST_BUFFER_FULL_PHRASE = 'Ingest buffer full at poll time.';
|
|
export const ARENA_TIMEOUT_PHRASE = 'Upstream Arena PLM request timed out.';
|
|
export const ARENA_MALFORMED_PHRASE = 'Upstream Arena PLM response was malformed.';
|
|
|
|
export function arenaStatusPhrase(status: number): string {
|
|
// A 2xx ArenaApiError is a response we could not interpret (e.g. login
|
|
// answered 200 without an arenaSessionId), not an upstream failure code.
|
|
if (status >= 200 && status < 300) return ARENA_MALFORMED_PHRASE;
|
|
return `Upstream Arena PLM request failed (HTTP ${status}).`;
|
|
}
|
|
|
|
export function publicErrorPhrase(err: unknown): string {
|
|
if (err instanceof ArenaApiError) return arenaStatusPhrase(err.status);
|
|
if (err instanceof IngestBufferFullError) return INGEST_BUFFER_FULL_PHRASE;
|
|
if (hasName(err, 'TimeoutError')) return ARENA_TIMEOUT_PHRASE;
|
|
return UNCLASSIFIED_ERROR_PHRASE;
|
|
}
|
|
|
|
/** Log-safe summary of an error: class name, message, and upstream status if any. */
|
|
export function describeErrorForLog(err: unknown): { name: string; message: string; status?: number } {
|
|
if (err instanceof ArenaApiError) return { name: err.name, message: err.message, status: err.status };
|
|
if (err instanceof Error) return { name: err.name, message: err.message };
|
|
return { name: typeof err, message: String(err) };
|
|
}
|
|
|
|
function hasName(err: unknown, name: string): boolean {
|
|
return typeof err === 'object' && err !== null && 'name' in err && (err as { name: unknown }).name === name;
|
|
}
|