client-api: stop logging suspended-database errors as 500s (#4918)

# Description of Changes

Disclaimer: This description was written by claude:

The two `.leader()` call sites in
[`client-api/src/routes/database.rs`](crates/client-api/src/routes/database.rs)
and
[`client-api/src/routes/subscribe.rs`](crates/client-api/src/routes/subscribe.rs)
pipe `GetLeaderHostError` through `log_and_500`, which:

1. Logs every error variant at `error` level — including `Suspended`,
`Bootstrapping`, `NoLeader`, etc., which are normal operational states.
This produces noisy log lines like:
   ```
ERROR /app/.../client-api/src/lib.rs:623: internal error: database is
suspended
   ```
2. Forces every such error into a **500 Internal Server Error**
response, even when the appropriate status code is something else (e.g.
503 Service Unavailable for a suspended database).

`GetLeaderHostError` already implements
`Into<axum::response::ErrorResponse>` with the correct per-variant
mapping:

| Variant | Status |
|---|---|
| `NoSuchDatabase` | 404 Not Found |
| `LaunchError`, `Misdirected` | 500 Internal Server Error |
| `NoNodeId`, `NoLeader`, `ControlConnection`, `Suspended`,
`Bootstrapping` | 503 Service Unavailable |

The standalone implementation already uses `?`-propagation directly.
This PR makes the two client-api call sites match that pattern.

## Result

- Suspended / bootstrapping / no-leader databases now return **503**
instead of 500.
- These expected states no longer produce `error`-level log spam in the
request path. Genuinely unexpected internal errors elsewhere in the
codebase continue to log via `log_and_500` unchanged.

# API and ABI breaking changes

None

# Expected complexity level and risk

1

# Testing

- [ ] Deploy to staging to see if we still see this error when trying to
access a suspended database
This commit is contained in:
John Detter
2026-04-30 11:24:02 -05:00
committed by GitHub
parent 8b8a42fa35
commit 280818631d
2 changed files with 2 additions and 2 deletions
+1 -1
View File
@@ -292,7 +292,7 @@ async fn find_leader_and_database<S: ControlStateDelegate + NodeDelegate>(
NO_SUCH_DATABASE
})?;
let leader = worker_ctx.leader(database.id).await.map_err(log_and_500)?;
let leader = worker_ctx.leader(database.id).await.map_err(Into::into)?;
Ok((leader, database))
}
+1 -1
View File
@@ -202,7 +202,7 @@ where
.map_err(log_and_500)?
.ok_or(StatusCode::NOT_FOUND)?;
let leader = ctx.leader(database.id).await.map_err(log_and_500)?;
let leader = ctx.leader(database.id).await.map_err(Into::into)?;
let identity_token = auth.creds.token().into();