|
| 1 | +--- |
| 2 | +'@objectstack/rest': minor |
| 3 | +--- |
| 4 | + |
| 5 | +fix(rest): four published doors refuse a `?limit=` they cannot read with `400 VALIDATION_FAILED`, instead of substituting, clamping or dropping it and answering `200` (#20061, #20062) |
| 6 | + |
| 7 | +Clause-②: no (narrowing) |
| 8 | + |
| 9 | +**BREAKING**: shipped as `minor` under the launch-window convention |
| 10 | +(`check-changeset-no-major` refuses `major` until GA). The banner and the ADR-0087 |
| 11 | +disposition below carry the breaking-ness, not the level. |
| 12 | + |
| 13 | +`GET /data/import/jobs`, `GET /data/:object/export`, `GET /meta/:type/:name/history` |
| 14 | +and `GET /search` read `?limit=` with a bare `Number()`. That coercion does not |
| 15 | +fail. It invents a value, and the door served it with a `200`: |
| 16 | +- `?limit=0` on the import-job history answered the 50-row default against a |
| 17 | + declaration of `min(1).max(200)`; |
| 18 | +- `?limit=abc` on the export downloaded one row; |
| 19 | +- on the metadata history it returned the whole change log; |
| 20 | +- on search it removed the overall cap. |
| 21 | + |
| 22 | +Since `@objectstack/client` sends `limit` exactly as the caller wrote it, the door |
| 23 | +is the only place such a value can be refused. |
| 24 | + |
| 25 | +Each door now reads the parameter against its own declaration: |
| 26 | +- **`GET /data/import/jobs`** reads `limit` and `offset` through |
| 27 | + `ListImportJobsRequestSchema` (`limit` `int().min(1).max(200)`, default 50; |
| 28 | + `offset` `int().min(0)`, default 0). |
| 29 | +- **`GET /meta/:type/:name/history`** reads `limit` through |
| 30 | + `HistoryMetaItemRequestSchema.limit` (`z.number()`, so any finite number, as |
| 31 | + before). |
| 32 | +- **`GET /data/:object/export`** and **`GET /search`** declare no request schema. |
| 33 | + There `limit` must be a whole number. Their range handling is unchanged: the |
| 34 | + export floor of 1 and cap of 50000, and search's `[1, 100]` clamp. |
| 35 | + |
| 36 | +A value outside the declaration answers `400` with the data surface's existing |
| 37 | +envelope, `{ error, code: 'VALIDATION_FAILED', fields }`. `fields[0].field` names |
| 38 | +the parameter. `fields[0].code` is the ADR-0114 member for the failed constraint: |
| 39 | +`invalid_type` for a value that is not a number (or not a whole one, where one is |
| 40 | +required), `min_value` or `max_value` for one outside a declared bound. The |
| 41 | +service is never called. |
| 42 | + |
| 43 | +What changes, per door (every row answered `200` before): |
| 44 | + |
| 45 | +| door | request | answered before | answers now | |
| 46 | +|:--|:--|:--|:--| |
| 47 | +| `GET /data/import/jobs` | `?limit=0`, `?limit=-3` | 50 rows / 1 row | `400`, `min_value` | |
| 48 | +| `GET /data/import/jobs` | `?limit=201`, `?limit=500` | 200 rows | `400`, `max_value` | |
| 49 | +| `GET /data/import/jobs` | `?limit=abc`, `?limit=1.5`, `?limit=Infinity` | 50 / 1.5 / 200 rows | `400`, `invalid_type` | |
| 50 | +| `GET /data/import/jobs` | `?offset=-1`, `?offset=abc`, `?offset=1.5` | offset 0 / 0 / 1.5 | `400`, `min_value` / `invalid_type` | |
| 51 | +| `GET /data/:object/export` | `?limit=abc`, `?limit=` (empty) | a one-row export | `400`, `invalid_type` | |
| 52 | +| `GET /data/:object/export` | `?limit=1.5`, `?limit=Infinity` | limit 1.5 / capped to 50000 | `400`, `invalid_type` | |
| 53 | +| `GET /meta/:type/:name/history` | `?limit=abc`, `?limit=Infinity` | the whole change log | `400`, `invalid_type` | |
| 54 | +| `GET /meta/:type/:name/history` | `?limit=` (empty) | zero events | `400`, `invalid_type` | |
| 55 | +| `GET /search` | `?limit=abc` | no overall cap | `400`, `invalid_type` | |
| 56 | +| `GET /search` | `?limit=1.5`, `?limit=Infinity` | a cap of 1.5 / 100 | `400`, `invalid_type` | |
| 57 | + |
| 58 | +A blank value such as `?limit=%20` is refused on every door. |
| 59 | + |
| 60 | +**Unchanged:** |
| 61 | +- An absent `limit` keeps each door's default: 50 jobs, a 10000-row export, the |
| 62 | + full history, search's 20. |
| 63 | +- An empty `?limit=` on the import-job history and on search still means absent, |
| 64 | + as it always did there. |
| 65 | +- Every conforming value reaches the service exactly as before, including the |
| 66 | + ranges no card here takes a position on: export `?limit=0` still exports one |
| 67 | + row, search `?limit=500` is still clamped to 100, and history still forwards |
| 68 | + `0` or `1.5` because its declaration admits them. |
| 69 | + |
| 70 | +**Fix for a caller that now gets the `400`:** send `limit` as a whole number, within |
| 71 | +the declared range where the door declares one (`1`–`200` for import jobs), or |
| 72 | +omit it to get the door's default. |
| 73 | + |
| 74 | +<!-- adr-0087: not-required (no-migration-prescription) Nothing authored moves: no spec key, metadata property or exported symbol is added, removed or renamed, and `packages/spec` is untouched, so `objectstack migrate meta` has nothing to rewrite and no ADR-0087 registry gains a row. What narrows is the set of HTTP query values four REST doors accept, back to what their declarations (`ListImportJobsRequestSchema`, `HistoryMetaItemRequestSchema`) or a whole-number reading already said. A query string is neither authored nor persisted. The other categories are closed on facts: `@objectstack/rest` publishes (not `unpublished`), no ADR-0087 id covers an HTTP query value (not `registered` / `already-registered`), and the change is runtime behaviour, not a TypeScript declaration (not `runtime-interface-only` / `type-surface-only`). --> |
0 commit comments