Add allowlisted sort and filter to scaffolded list views#1854
Conversation
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0ad5c20597
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| #[get("/{plural}")] | ||
| pub async fn index( | ||
| list_query: ListQuery, | ||
| RawQuery(raw_query): RawQuery, |
There was a problem hiding this comment.
Preserve filters when paging scaffolded lists
In a generated non-live scaffold, visiting something like /posts?filter[published]=true&sort=title&dir=desc&page=1 and then clicking a pagination link drops the filter/sort, because the RawQuery added here is only passed to data_table while the pager below still renders with PagerOptions::new("/{plural}") and no .query(...). Thread the same raw query into pagination_nav (and the sharded branch) so repo.list(...) keeps applying the current ListQuery across pages.
Useful? React with 👍 / 👎.
Give app authors a first-class, injection-safe way to turn `?sort=`, `?dir=`, and `?filter[col]=` query params into an ordered/filtered list query, so a scaffolded index sorts + filters from the URL with zero hand-written ORDER BY / WHERE and no SQL-injection footgun. Before: - `PageRequest` carried only `page`/`size`; the `data_table` widget rendered `?sort=name&dir=asc` links pointing at a capability the framework didn't provide. Authors hand-wrote param -> ORDER BY/WHERE translation — boilerplate, and a dynamic-ORDER-BY injection risk. - The generated `index` handler called `repo.page(&page_req)` (hardcoded `id DESC`), so sort links were dead. After: - autumn-web: new `ListQuery` extractor (pagination.rs) parses `sort`, `dir`, and `filter[col]=val`, best-effort with `Rejection = Infallible` (never 400s), mirroring `PageRequest`. `SortDir` is now canonical here and re-exported from `widgets`. Docs call out the allowlist as the security boundary; a `rust,ignore` example shows the composition. - autumn-macros: `#[model]` emits typed, per-column boxed-query helpers (`__autumn_list_apply_filters` / `__autumn_list_apply_order`) whose allowlist is the model's own columns; `#[repository]` emits `list(&ListQuery, &PageRequest) -> Page<Model>` alongside `page()`, built on `table.into_boxed()` + typed Diesel DSL (no string interp), filters applied to both COUNT and page query, default order = PK desc. An unknown/malicious `sort`/`filter` key hits the default match arm and is ignored — it can never reach SQL. A blanket no-op `AutumnListable` fallback keeps `list()` available on hand-written (non-`#[model]`) repositories (paginate without sort/filter). - autumn-cli: the non-live `index` extracts `ListQuery` + `RawQuery`, calls `repo.list(..)`, marks scalar columns `.sortable(..)`, and wires `DataTableConfig.query/active_sort/active_dir` symmetric with the rendered links. `--live` (SSE `<ul>`) is gated off with a comment; `--live-validation` and `--sharded` get the full wiring. Tests: structural injection proof (model macro expansion asserts the allowlist is typed columns only, unknown keys fall to the PK default, and no sql_query/ORDER BY/format! path exists), repository `list()` shape test, `ListQuery` parse/coercion + never-400 extractor tests, scaffold unit tests, and a `scaffold_sort_filter` integration test across the plain/live/live-validation/sharded variants. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012CMReMk2hrKvkzNvPQNsEg
0ad5c20 to
64205b9
Compare
Closes #1126
Before/After
Before — the
data_tablewidget rendered?sort=col&dir=asclinks but the framework had no safe way to turn those params into an ordered/filtered query. App authors hand-wrote query-param→ORDER BY/WHEREtranslation, an SQL-injection footgun.After — a
ListQueryrequest extractor parsessort/dir/filter[col]=val(best-effort, never 400s), and the repository gains a typedlist(&ListQuery, &PageRequest) -> Pagebuilt with Diesel's.into_boxed()so only allowlisted columns (the model's own columns) can ever reachORDER BY/WHERE. An unknown or malicioussort(e.g.id;DROP TABLE) falls through to the stable default (allowlisted column then PK) and never reaches SQL.autumn generate scaffoldwires the index handler tolist(...), marks columns.sortable(..), and configuresdata_tablesort state symmetrically.How
ListQuery/SortDirinautumn/src/pagination.rs(Infallible extractor).#[model]emits typed per-column boxed-query allowlist helpers and#[repository]emitslist()(tenant + non-tenant, soft-delete/shard aware).autumn-cli/src/generate/scaffold.rs(--livegated off with a comment; filters limited to non-null String/int/bool equality per issue scope).sql_query/ORDER BY/format!appears in the helper region.Testing
cargo fmt --all -- --check,cargo clippy --workspace --all-targets -- -D warnings, andcargo test -p autumn-web -p autumn-macros -p autumn-cliall green.--live-validation/--shardedappscargo checkclean.Draft — pushed as a backup and held per the team merge queue (#1825 → #1236 → #1126). Will be rebased onto the latest
trunk-devand marked ready once #1236 lands. Do not merge yet.Generated by Claude Code