# RB-16 — `DateOnly.TryParse` on `?peildatum=` Status: **implemented** · 2026-08-27 · Source findings: `07-bio2-compliance.md` BIO-019 · `99-backlog.md` RB-16 ## What was wrong `Program.cs:215` (pre-change) — `var rows = peildatum is { Length: > 0 } p ? t.RowsOn(DateOnly.Parse(p)) : t.Rows();`. `DateOnly.Parse` throws `FormatException` on anything unparseable; there was no `TryParse`, no 400 path, and `.Produces` on the endpoint declared only 200/403/404 — so an unparseable `?peildatum=` value 500'd, and in Development the exception detail was returned to the caller. §3c's baseline named `backend/Stamdata` 96.8% line but **71.7% branch** (BL-005) — this was one of the unentered branches. ## What changed | File | Change | | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Program.cs` — `GET /stamdata/{table}` | `DateOnly.Parse` replaced with `DateOnly.TryParse`; an unparseable value now returns `Results.Problem(detail: …, statusCode: 400)` instead of throwing; endpoint doc gained `.ProducesProblem(StatusCodes.Status400BadRequest)` | | `tests/BigRegister.Tests/StamdataEndpointTests.cs` | **new** `Unparseable_peildatum_is_400_not_500` | | `backend/swagger.json`, `libs/shared/src/infrastructure/api-client.ts` | regenerated (`npm run gen:api`) — the new 400 response is now part of the documented contract | ## What the fix looks like ```csharp DateOnly? peildatumWaarde = null; if (peildatum is { Length: > 0 } p) { if (!DateOnly.TryParse(p, out var parsed)) return Results.Problem(detail: $"Ongeldige peildatum '{p}'.", statusCode: StatusCodes.Status400BadRequest); peildatumWaarde = parsed; } var rows = peildatumWaarde is { } d ? t.RowsOn(d) : t.Rows(); ``` Matches the shape every other bad-input check in this file already uses (e.g. the upload endpoint's `Results.Problem(detail: …, statusCode: 400)` for a malformed multipart request) — a `Results.Problem` with a Dutch detail message, not a bespoke response shape. ## Judgement calls - **No FE change needed, and none made.** `libs/beheer/src/infrastructure/ stamdata.adapter.ts`'s `load()` already routes every call through `runSubmit` (`libs/shared/src/application/submit.ts`), which try/catches any thrown `ApiException` — including the client's new 400 branch — into a generic `Result` error string via `problemDetail`. There is no status-code-specific branching to extend; ADR-0001's "the FE renders the decision, it does not recompute the rule" already covers "the server rejected this input" as a case the generic error path handles, same as the existing 403. - **Regenerated the API client and committed it in this ticket's diff**, rather than leaving it to drift. RB-09's implementation note records a real prior incident where a response-shape change (RB-08's 403 → `ProducesProblem`) landed without a regeneration and the drift went unnoticed until the next ticket's `gen:api` run. This ticket's `.ProducesProblem(400)` is exactly that same category of change, so `npm run gen:api` was run immediately as part of implementing it, not deferred. - **The Dutch detail message follows the file's own convention** (`$"Ongeldige peildatum '{p}'."`) rather than English — every other `Results.Problem(detail: …)` call in `Program.cs` (change-request rejection, upload validation, submit rejection) is Dutch; this is server-internal wire text, not `$localize`-wrapped UI copy (the FE never renders it verbatim — CLAUDE.md's `$localize` rule is about user-facing copy the FE owns, not backend `ProblemDetails.detail` strings), so no locale entry was needed. ## Verification - **Reverted the fix only** (`DateOnly.Parse` restored, ternary un-nested, via Edit — test left in place) and ran `StamdataEndpointTests`: `Unparseable_peildatum_is_400_not_500` failed red (`Expected: BadRequest, Actual: InternalServerError` — confirming the endpoint really did 500, not some other status). Restored the fix (via Edit) and re-ran: all 6 tests in the class green. - `dotnet build`: clean, 0 warnings. - `dotnet format BigRegister.slnx --verify-no-changes`: clean. - `dotnet test --filter "Category!=Integration"`: **260 passed, 0 failed** (259 + 1 new). - `npm run gen:api`: exit 0; `backend/swagger.json` gained the 400 response shape for this one endpoint; `libs/shared/src/infrastructure/api-client.ts` gained the matching `status === 400` branch. Both regenerated files committed alongside the code change. - `npm test` (all four Vitest projects — ssp/behandelportal/shared/beheer): **445 passed, 0 failed**, confirming the regenerated client doesn't break any existing FE consumer of `stamdataTable(...)`.