Skip to content

feat: classify driver errors into client/transient/server for accurate HTTP status codes#170

Open
eugene-mobile wants to merge 1 commit into
mainfrom
claude/issue-122-20260523-1334
Open

feat: classify driver errors into client/transient/server for accurate HTTP status codes#170
eugene-mobile wants to merge 1 commit into
mainfrom
claude/issue-122-20260523-1334

Conversation

@eugene-mobile

Copy link
Copy Markdown
Contributor

Implements #122: introduces DriverError with errorClass so the query route returns 400/503/500 instead of always 400.

Changes:

  • interface.ts: DriverErrorClass + DriverError class
  • postgres.ts: classifyPgError() maps pg error codes to classes
  • mongodb.ts: explicit throws converted to DriverError, Mongo errors classified by type
  • query.ts: catch block maps errorClass to HTTP status
  • Tests updated and extended

Closes #122

Generated with Claude Code

…e HTTP status codes

Introduces DriverError with errorClass ('client' | 'transient' | 'server')
so the query route can return 400, 503, or 500 rather than always 400.

- interface.ts: add DriverErrorClass type and DriverError class
- postgres.ts: wrap query/queryAll errors using pg error codes
  (08xxx/53xxx/57xxx → transient, 42xxx/28xxx → client, rest → server)
- mongodb.ts: convert explicit throws in parseRequest/operations to
  DriverError('client'), wrap operation errors via MongoNetworkError/
  MongoServerSelectionError/MongoParseError type checks
- query.ts: map errorClass to HTTP status (client→400, transient→503,
  server→500); unclassified errors default to 500
- Tests updated and extended for all three error classes

Closes #122

Co-authored-by: Eugene <eugene-mobile@users.noreply.github.com>
@eugene-mobile

Copy link
Copy Markdown
Contributor Author

@claude review this PR

@claude

claude Bot commented May 23, 2026

Copy link
Copy Markdown

Claude finished @eugene-mobile's task in 1m 59s —— View job


PR Review: feat: classify driver errors into client/transient/server for accurate HTTP status codes

  • Gather context and read changed files
  • Review interface.ts changes
  • Review postgres.ts changes
  • Review mongodb.ts changes
  • Review query.ts changes
  • Review test files
  • Post comprehensive feedback

Overall this is a clean, well-structured implementation that meaningfully improves error signal fidelity. The architecture is correct, the guard against double-wrapping is well-placed, and the test coverage is thorough. A few findings below — one substantive gap worth fixing, one debatable classification, and some minor observations.


interface.ts — No issues

DriverErrorClass and DriverError are minimal and correct. Using ErrorOptions for cause chaining is the right approach.


postgres.ts — One notable gap

Integrity constraint violations (23xxx) classified as server (500)

23xxx codes are not matched by any branch in classifyPgError, so they fall through to new DriverError(message, 'server') → HTTP 500. But these are user-data errors caused by the client's input: duplicate key (23505), NOT NULL violation (23502), foreign key violation (23503), check constraint violation (23514). They should be client → 400, not server → 500.

// server/src/drivers/postgres.ts:31-34
if (code.startsWith('42') || code.startsWith('28')) {
  return new DriverError(message, 'client', { cause: err });
}

Suggested fix — add 23 to the client branch: Fix this →

Transaction rollback codes (40xxx) classified as server (500)

40001 (serialization failure) and 40P01 (deadlock) are arguably transient — they're retryable concurrency conditions, not bugs. Classifying them as server causes the client to see a 500 when the right signal would be "retry later." Whether to address this now is a judgment call, but it's worth noting.

Operator intervention 57014 (query_canceled)

This is bundled with the rest of 57xxx as transient, which is defensible — most callers will want to retry a canceled query. Just worth noting that if the user explicitly canceled it themselves, a retry loop on the client would be surprising. Acceptable tradeoff for now.


mongodb.ts — Good coverage with one minor gap

MongoNetworkError, MongoServerSelectionError, and MongoParseError are the most common failure modes and are handled. Worth knowing that MongoWriteConcernError (replication failures) would fall through to server today — these can be transient. Not blocking for this PR but a follow-up candidate.

The parseRequest function throwing DriverError('client') on bad JSON/missing fields is exactly right and integrates neatly with the catch-rethrow guard.


query.ts — Correct

let status = 500;
if (err instanceof DriverError) {
  if (err.errorClass === 'client') status = 400;
  else if (err.errorClass === 'transient') status = 503;
}

The default of 500 for both DriverError('server') and non-DriverError is correct. One minor enhancement worth considering: returning a Retry-After header on 503 would help clients implement safe backoff, but that's outside the scope of this PR.


Tests — Solid

  • Coverage of all three errorClass values at the route level (400/503/500) in query.test.ts is complete.
  • The "re-throws DriverError without double-wrapping" tests in both postgres.test.ts and mongodb.test.ts are a particularly important guard — good to see those present.
  • The pg error code range tests cover the major categories. One gap: no test for 23xxx (constraint violation) → server (which is currently the behavior, but should be client after the fix above).

Summary

Severity Finding
Notable 23xxx Postgres constraint violations classified as server (500) instead of client (400)
Minor 40xxx transaction conflicts classified as server rather than transient
Minor No Retry-After header on 503 responses
Info MongoWriteConcernError falls through to server classification
Info ping() / getSchemas() / etc. are not wrapped — acceptable if those routes have their own error handling

The 23xxx gap is the most concrete correctness issue: a user sending a row that violates a unique constraint gets a 500 today (which signals a bug on our end) instead of a 400 (which correctly signals bad input). Everything else is either a minor improvement opportunity or informational.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Classify driver errors into 4xx vs 5xx in query route

1 participant