Skip to content

Add allowlisted sort and filter to scaffolded list views#1854

Merged
madmax983 merged 1 commit into
trunk-devfrom
wave10-sort-filter
Jul 13, 2026
Merged

Add allowlisted sort and filter to scaffolded list views#1854
madmax983 merged 1 commit into
trunk-devfrom
wave10-sort-filter

Conversation

@madmax983

Copy link
Copy Markdown
Owner

Closes #1126

Before/After

Before — the data_table widget rendered ?sort=col&dir=asc links but the framework had no safe way to turn those params into an ordered/filtered query. App authors hand-wrote query-param→ORDER BY/WHERE translation, an SQL-injection footgun.

After — a ListQuery request extractor parses sort/dir/filter[col]=val (best-effort, never 400s), and the repository gains a typed list(&ListQuery, &PageRequest) -> Page built with Diesel's .into_boxed() so only allowlisted columns (the model's own columns) can ever reach ORDER BY/WHERE. An unknown or malicious sort (e.g. id;DROP TABLE) falls through to the stable default (allowlisted column then PK) and never reaches SQL. autumn generate scaffold wires the index handler to list(...), marks columns .sortable(..), and configures data_table sort state symmetrically.

How

  • ListQuery/SortDir in autumn/src/pagination.rs (Infallible extractor).
  • #[model] emits typed per-column boxed-query allowlist helpers and #[repository] emits list() (tenant + non-tenant, soft-delete/shard aware).
  • Scaffold wiring in autumn-cli/src/generate/scaffold.rs (--live gated off with a comment; filters limited to non-null String/int/bool equality per issue scope).
  • Framework injection test asserts the allowlist is typed and no sql_query/ORDER BY/format! appears in the helper region.

Testing

  • cargo fmt --all -- --check, cargo clippy --workspace --all-targets -- -D warnings, and cargo test -p autumn-web -p autumn-macros -p autumn-cli all green.
  • Generated default / --live-validation / --sharded apps cargo check clean.

⚠️ Status

Draft — pushed as a backup and held per the team merge queue (#1825#1236#1126). Will be rebased onto the latest trunk-dev and marked ready once #1236 lands. Do not merge yet.


Generated by Claude Code

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@madmax983 madmax983 marked this pull request as ready for review July 13, 2026 06:49
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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
@madmax983 madmax983 force-pushed the wave10-sort-filter branch from 0ad5c20 to 64205b9 Compare July 13, 2026 08:06
@madmax983 madmax983 merged commit b678b9a into trunk-dev Jul 13, 2026
29 checks passed
@madmax983 madmax983 deleted the wave10-sort-filter branch July 13, 2026 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add allowlisted sort and filter to scaffolded list views from params

2 participants