|
| 1 | +# ADR 0002: Shared error resolution for the write and query paths |
| 2 | + |
| 3 | +- Status: Accepted (implemented for the write path in the 1.4.1 patch) |
| 4 | +- Date: 2026-09-01 |
| 5 | + |
| 6 | +## Context |
| 7 | + |
| 8 | +`WriteService.handleWriteError` and `QueryService.handleQueryError` |
| 9 | +(`src/services/write.service.ts`, `src/services/query.service.ts`) both |
| 10 | +convert a thrown HTTP error into the error message returned to the MCP |
| 11 | +client. The two implementations diverged. `handleQueryError` resolves the actual InfluxDB |
| 12 | +error body (`data.message` → `data.error` → string body → `statusText` → |
| 13 | +`error.message`). `handleWriteError` matched only on |
| 14 | +`error.response?.status` and threw a fixed string per status; the response |
| 15 | +body was discarded. Its fallback interpolated `error.response?.data` |
| 16 | +directly, which rendered a parsed JSON body as `[object Object]`. |
| 17 | + |
| 18 | +InfluxDB 3.11 rejects a duplicate-tag-key write with a body that names the |
| 19 | +tag, nested under `data.data[].error_message`. The fixed 400 string |
| 20 | +discarded that body, so the tag name was not returned to the client. Neither |
| 21 | +handler had a 503 arm. The two write-capable transports throw different |
| 22 | +error shapes: axios (`error.response.status`/`.data`) for |
| 23 | +Core/Enterprise/Clustered, and the InfluxDB SDK's `HttpError` |
| 24 | +(`error.statusCode`, `error.json`/`.body`) for Cloud Dedicated/Serverless. |
| 25 | +`handleWriteError` matched only the axios shape, so cloud write errors |
| 26 | +always fell through to the fallback. |
| 27 | + |
| 28 | +Three other services preserve error bodies with their own separate |
| 29 | +implementations: `token-management.service.ts`, |
| 30 | +`database-management.service.ts`, `cloud-token-management.service.ts`. |
| 31 | + |
| 32 | +## Decision |
| 33 | + |
| 34 | +Extract the shared logic into `src/services/error-resolution.service.ts`: |
| 35 | + |
| 36 | +- `normalizeError(error)` converts the axios shape and the SDK `HttpError` |
| 37 | + shape to one `{ status, body }` form. |
| 38 | +- `resolveErrorMessage(body, fallback)` resolves the error message from a |
| 39 | + body: the write path's partial-write shapes first, then the query path's |
| 40 | + existing resolution order, then `fallback`. |
| 41 | + |
| 42 | +`handleWriteError` calls both and switches on the normalized status, |
| 43 | +including a new `503` arm phrased as retryable. `handleQueryError` and the |
| 44 | +three token/database services are unchanged in this patch. The helper's |
| 45 | +default resolution order already matches `handleQueryError`'s current |
| 46 | +behavior, so migrating it, and the other three services, is deferred to a |
| 47 | +later change. |
| 48 | + |
| 49 | +## Consequences |
| 50 | + |
| 51 | +- One function defines the write path's error-body resolution for all five |
| 52 | + product types, instead of five status arms each with its own field |
| 53 | + access. |
| 54 | +- A future status code that needs a body-preserving arm extends |
| 55 | + `resolveErrorMessage`'s resolution order once, for both paths. |
| 56 | +- `handleQueryError` and the three token/database services keep their own |
| 57 | + implementations. That is a known follow-up, not a regression introduced |
| 58 | + by this change. |
0 commit comments