Fix #015 (nested-JSON 500) and #016 (ragged CSV data loss)

#015 — deeply nested JSON raised RecursionError (a RuntimeError, not
ValueError) and became an unhandled 500 on two paths:
- json-kind content validation now catches RecursionError -> 400.
- Added a custom DRF EXCEPTION_HANDLER
  (artifacts/exception_handlers.py, wired via REST_FRAMEWORK) mapping
  otherwise-unhandled RecursionError -> 400, covering the parser-level
  crash on deeply nested request bodies (metadata etc.) too.

#016 — wrap_csv_viewer handed Grid.js rows without reconciling width to the
header, so over-long rows silently dropped cells. Now normalizes every row
to the max width: long rows widen the columns (auto-named "Column N"),
short rows pad with "". No cell is dropped.

Both verified live; 4 regression tests added. 106 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
peter 2026-09-03 10:57:07 +01:00
parent 5c53643349
commit 5f872b4b32
6 changed files with 110 additions and 7 deletions

View file

@ -1,6 +1,6 @@
# 015 — Deeply nested JSON raises `RecursionError` → unhandled HTTP 500 (two code paths)
**Status:** Open
**Status:** Fixed
**Severity:** Medium — unhandled 500 where a 400 is correct; small (~30 KB)
request crashes the handling worker (minor DoS shape); full traceback
exposed when `DEBUG=True`.
@ -91,4 +91,21 @@ this.
## Resolution
_(pending)_
Both paths fixed:
- **Path 1** (`json`-kind content validation): `validate_content_shape` now
catches `RecursionError` alongside `(TypeError, ValueError)`, returning
the same `400 content must be valid JSON: …`.
- **Path 2** (deep nesting anywhere in the request body, crashing DRF's
parser before the view): added a custom DRF `EXCEPTION_HANDLER`
(`artifacts/exception_handlers.py`, wired via `REST_FRAMEWORK` in
settings) that maps otherwise-unhandled `RecursionError` to
`400 Request payload is nested too deeply to parse.` This also covers the
same class of error in `metadata`/`tag_colors` string parsing. Anything
that isn't a RecursionError still falls through to Django's normal 500.
Verified live: deep `json` content and a ~5000-deep `metadata` body both
return 400; shallow payloads still 201. Tests added in
`tests/api/test_artifacts.py`. (A depth-cap parser was considered but the
exception-handler approach is simpler and covers every recursive-descent
site at once; prod `DEBUG=False` already suppresses the traceback page.)

View file

@ -1,6 +1,6 @@
# 016 — Ragged CSV rows silently lose data in the rendered table (server doesn't normalize row width)
**Status:** Open
**Status:** Fixed
**Severity:** Low-Medium — silent data loss in the primary rendered view;
raw sidecar is unaffected, so it's a fidelity/display bug, not permanent
loss.
@ -82,4 +82,13 @@ raw sidecar.
## Resolution
_(pending)_
Implemented option (b) in `wrap_csv_viewer`: normalize every row to the
maximum row width before handing data to Grid.js. Rows longer than the
header widen the column set (extra columns auto-named `Column N`); rows
shorter than the width are padded with empty strings. No cell is ever
dropped, matching "server owns normalization, never silently lose content."
Verified live with the issue's exact input (`a,b,c` / `1,2,3,4,5` / `6,7`):
grid data becomes `columns=[a,b,c,Column 4,Column 5]`,
`data=[[1,2,3,4,5],[6,7,"","",""]]` — the previously-dropped `4` and `5`
now render. Test added in `tests/api/test_artifacts.py`.