You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit c67a3de
Browse filesBrowse the repository at this point in the historyBrowse files
fix: report list routes return all rows up to a uniform cap (#465)
* fix(reporting): report list routes return all rows up to a uniform cap, refuse bad bounds
Report list routes in all five ports now return every row up to a
generator-configurable cap (default 10000) instead of a silent 50-row page.
Above the cap, or with an explicit limit above it, the route answers
400 report_too_large with maxRows. limit<=0, negative offset and
non-integer bounds are refused with the existing 400 envelope (C# used to
ignore them). Pinned by new api-contract report scenarios.
* no-mistakes(review): Fix TS report limit error text; rename Java test
* no-mistakes(test): Fix report cap test filterAllowlist type to typecheck
* no-mistakes(document): Docs checked against report row cap change. CHANGELOG, api-contract.md and reporting.md already updated by the change and match code (cap 10,000, reportMaxRows option names, report_too_large and pagination.invalid_value envelopes, 30-scenario count). Fixed one formatting nit: missing blank line between Fixed entries in CHANGELOG [Unreleased]. docs/superpowers/plans/2026-10-04-fr-044-plan-3 still describes the old 50-row default, but it is a dated plan record, so left as is.
Copy file name to clipboardExpand all lines: docs/features/api-contract.md
+10-4Lines changed: 10 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -206,15 +206,15 @@ feature demand (it is a consistency/safety divergence, not a capability).
206
206
The TypeScript mount helpers (`@metaobjectsdev/runtime-ts/drizzle-fastify`,
207
207
`/fastify` and `/hono`) pin five responses the contract leaves open — HTTP 5xx is
208
208
implementation-defined below, and no corpus scenario sends a malformed body, a write the database rejects, a
209
-
malformed page bound, or a value only the generated schema's format check refuses. All five use the contract's
209
+
malformed page bound to an entity, or a value only the generated schema's format check refuses. All five use the contract's
210
210
`{ "error": "<code>" }` envelope, and all five are scoped to the
211
211
routes the helpers mount: an adopter's own routes, and a Fastify `setErrorHandler`
212
212
or Hono `onError` the adopter installed, answer exactly as they did before.
213
213
214
214
| Response | When |
215
215
|---|---|
216
216
| malformed JSON body | a `POST`/`PATCH`/`PUT` body that does not parse as JSON (an empty body sent as `application/json` included) → HTTP 400 `{ "error": "invalid_json" }`. An EMPTY body on a method that takes none (`GET`, `DELETE`, `HEAD`) is accepted even when the request declares `content-type: application/json`, as many clients do on every request; a non-empty `DELETE` body is still parsed. Before, Fastify answered its own `{ "statusCode": 400, "code": "FST_ERR_CTP_INVALID_JSON_BODY", … }` and Hono a Zod `validation` error about a missing object |
217
-
| malformed page bound | a `?limit=` or `?offset=` that is not a non-negative integer (`abc`, `-5`, `1.5`, an empty value) → HTTP 400 `{ "error": "pagination.invalid_value", "param": "limit", "value": "abc", "expected": "a non-negative integer (0 or more)" }`. Before, a non-numeric bound was dropped (every row came back) and a negative or fractional one reached SQL. There is no page-size maximum on an entity or a projection with declared columns; an opaque view (no declared columns, served by raw SQL) pages to at most 1000 rows, so there a `limit` above 1000 is refused too, with `"expected": "an integer from 0 to 1000"` (it used to be clamped silently). The same answer comes from the Drizzle mounts and the ObjectManager Fastify mount. No corpus scenario sends a malformed bound; the other ports answer it as their framework does |
217
+
| malformed page bound | a `?limit=` or `?offset=` that is not a non-negative integer (`abc`, `-5`, `1.5`, an empty value) → HTTP 400 `{ "error": "pagination.invalid_value", "param": "limit", "value": "abc", "expected": "a non-negative integer (0 or more)" }`. Before, a non-numeric bound was dropped (every row came back) and a negative or fractional one reached SQL. There is no page-size maximum on an entity or a projection with declared columns; an opaque view (no declared columns, served by raw SQL) pages to at most 1000 rows, so there a `limit` above 1000 is refused too, with `"expected": "an integer from 0 to 1000"` (it used to be clamped silently). The same answer comes from the Drizzle mounts and the ObjectManager Fastify mount. The corpus sends a malformed bound only to a report (`report/bad-page-bound`), where every port answers this same envelope and also refuses `limit=0`; on an entity or a projection the other ports answer it as their framework does |
218
218
| database constraint violation | a write the database itself rejects → `{ "error": "constraint_violation", "constraint": "<kind>" }`, HTTP **409** for `unique` and `foreign_key` (a conflict with another row) and HTTP **400** for `check` and `not_null` (the request's own value), per the status rule under "Error response". The body names the constraint KIND only — never the SQL, the table or the bound values. A `CHECK` the model derives (an enum, a `validator.numeric`/`length`/`regex` range) is normally refused earlier by the route's own validation, as the corpus's `{ "error": "validation" }` 400; this row is the database's backstop |
219
219
| schema validation failure | a body that fails the insert/update schema → HTTP 400 `{ "error": "validation", "issues": [...] }`, each issue as Zod reports it **minus `pattern`**: Zod 4 attaches a failed `.regex()` check's source there, which for a generated `field.date` / `field.time` / `field.timestamp` check is a ~300-character calendar regex (and an authored `validator.regex` pattern is the server's to keep). The generated temporal checks carry a short `message` naming the expected format instead (`"must be an ISO date (YYYY-MM-DD)"`) |
220
220
| unexpected server error | anything that is not a filter, validation, not-found or constraint answer — a query against a column the database no longer has, a driver failure → HTTP 500 `{ "error": "internal" }`, the code the cross-port reference servers already use. The body names no SQL, table, column or bound parameter; the full error goes to the server log (`console.error`). Before, Fastify's default handler echoed the driver message, which for Drizzle is the query text and its parameter values |
@@ -250,7 +250,13 @@ each handler they register instead; an `HTTPException` is rethrown to your
250
250
-`limit=N` — page size.
251
251
-`offset=N` — page offset. Both are non-negative integers; the TS mounts refuse
252
252
anything else with a 400 (see "TS-only error responses").
253
-
-**No page-size maximum, and the default is per port.** The contract pins what
253
+
-**A report is the exception: one cap in every port.** A served `object.report` list returns every
254
+
row when `limit` is omitted, up to 10,000 (a generator option), and answers
255
+
`400 { "error": "report_too_large", "maxRows": N }` for one row more, or for a `limit` above the cap.
256
+
A `limit` below 1, a negative `offset` or a value that is not an integer is
0 commit comments