Skip to content

fix(cql2-rewrite-links): preserve client filter from POST body#162

Open
nicolaracco wants to merge 1 commit intodevelopmentseed:mainfrom
condensetech:fix/cql2-rewrite-links-post-body-filter
Open

fix(cql2-rewrite-links): preserve client filter from POST body#162
nicolaracco wants to merge 1 commit intodevelopmentseed:mainfrom
condensetech:fix/cql2-rewrite-links-post-body-filter

Conversation

@nicolaracco
Copy link
Copy Markdown

Cql2RewriteLinksFilterMiddleware strips filter and filter-lang from the body of paginated next links when the client originally sent the filter inside a POST /search body. Following the next link therefore loses the filter, broadening every page after the first.

The middleware was reading the client's original filter only from request.query_params, which is empty for POST search. With nothing to restore, it fell into the strip branch and popped the fields outright.

Reproduction

curl -X POST https://<proxy>/search \
  -H 'content-type: application/json' \
  -d '{
    "collections": ["my-collection"],
    "limit": 10,
    "filter": {"op":"=","args":[{"property":"processing:version"},"A01"]},
    "filter-lang": "cql2-json"
  }'

next.body returned by the proxy:

{ "collections": ["my-collection"], "limit": 10, "token": "..." }

vs the same call against the upstream STAC API directly, which returns:

{
  "collections": ["my-collection"],
  "limit": 10,
  "token": "...",
  "filter": {"op":"=","args":[{"property":"processing:version"},"A01"]},
  "filter-lang": "cql2-json"
}

Fix

Symmetric with the existing query-string read:

  1. For any POST/PUT/PATCH (no path scoping — the JSON-decode no-ops if the body is absent or unparseable, mirroring how query_params.get("filter") no-ops when the key is missing), drain the request body, JSON-decode it, and capture body["filter"] and body["filter-lang"] using a sentinel so we can distinguish "client sent no filter" from "client sent some value".
  2. Replay the buffered bytes via a wrapped receive so Cql2ApplyFilterBodyMiddleware and the upstream still see the unmodified body.
  3. When rewriting each link.body, prefer the captured client values over the existing query-string-derived behavior; fall back to the prior pop-when-stripping-only path otherwise.

The query-string href branch is unchanged.

Cql2RewriteLinksFilterMiddleware read the client's original filter only
from request.query_params, which is empty for POST /search where the
filter lives in the JSON body. With no original filter to restore, the
middleware fell into the strip-from-body branch and popped both `filter`
and `filter-lang` from every paginated next-link body. Clients
following `next` silently lost their filter, broadening every page
after the first.

Capture the client's `filter` and `filter-lang` from the POST/PUT/PATCH
request body for endpoints matching ^/search$ (kept in lock-step with
Cql2ApplyFilterBodyMiddleware.search_body_endpoints), echo them back
verbatim into next-link bodies, and replay the buffered bytes via a
wrapped receive so inner middlewares still see the unmodified body.
Existing GET-with-query-string behavior is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the fix label May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant