Skip to content

fix(event_handler): route VPC Lattice requests that carry a query string - #8572

Merged
leandrodamascena merged 2 commits into
aws-powertools:developfrom
AtulJoshi1206:fix/vpc-lattice-path-query-string
Oct 17, 2026
Merged

leandrodamascena merged 2 commits into
aws-powertools:developfrom
AtulJoshi1206:fix/vpc-lattice-path-query-string

Conversation

@AtulJoshi1206

Copy link
Copy Markdown
Contributor

Issue number: closes #8571

Summary

Changes

VPC Lattice puts the query string inside the request path (raw_path in V1, path in V2) and also sends it parsed. VPCLatticeEvent.path and VPCLatticeEventV2.path returned that value verbatim, and the resolvers match routes against event.path with anchored regexes. So any request with a query string got a 404 on VPCLatticeResolver and VPCLatticeV2Resolver, sync and async.

This makes path the URL path without the query string, as it is for REST API, HTTP API, Function URL and ALB events.

  • V1: VPCLatticeEvent.path returns raw_path up to the first ?. raw_path itself is unchanged, and its docstring now says it includes the query string.
  • V2: VPCLatticeEventV2 gets its own path property, returning path up to the first ?. The original value stays in event.raw_event["path"].
  • Split rule: per RFC 3986, the first ? starts the query, so an encoded %3F stays in the path.
  • Why the data classes: path feeds routing, strip_prefixes, Swagger UI's ?format=json and user code, so fixing it there gives all of them the corrected value.
  • New fixtures, with the existing ones unchanged so their tests keep their meaning:
  • 17 new tests. All fail on develop and pass with this change:
    • Data classes:
      • V1 and V2 path exclude the query string, and V1 raw_path keeps it.
      • Edge cases: the root path, an empty query, a ? inside the query, and an encoded %3F in the path.
      • test_vpc_lattice_event now also checks that a path without a query is unchanged.
    • Resolvers:
      • Both route a request whose path carries the query string.
      • /orders/<order_id> with /orders/42?expand=items gives order_id == "42".
      • resolve_async is covered.
      • Both resolvers work with enable_validation=True and Annotated[int, Query(alias="order-id")].

Checks:

  • event_handler, data_classes and parser suites (unit and functional): all pass.
  • ruff check and ruff format --check: clean.
  • mypy on vpc_lattice.py: clean.

Evidence that VPC Lattice sends the query string in the path:

  • AWS VPC Lattice User Guide:
    • V1: "raw_path": "/path/to/resource?query1=value1&query2=value2".
    • V2: "path": "/?query1=value1&query2=value2", "The path of the request from the client that includes query string parameters."
  • AWS Compute Blog: "raw_path": "/points?action=add".
  • powertools-lambda-typescript#5764: a live V2 capture with path: '/echo?QS1=value1&QS1=value2&single=one'.

User experience

Before, for the V2 event captured in powertools-lambda-typescript#5764:

app = VPCLatticeV2Resolver()

@app.post("/echo")
def echo():
    return {"query": app.current_event.query_string_parameters}

app.resolve(event, context)  # event["path"] == "/echo?QS1=value1&QS1=value2&single=one"
# statusCode 404, body {"statusCode":404,"message":"Not found"}

After: the request reaches echo and returns 200 with {"query": {"QS1": "value1,value2", "single": "one"}}, and app.current_event.path == "/echo". Dynamic routes, strip_prefixes and /swagger?format=json now match the same way, and V1 behaves the same.

Risk: event.path no longer contains the query string.

  • Code that parsed the query out of event.path should use event.query_string_parameters, or the raw value: event.raw_path (V1) or event.raw_event["path"] (V2).
  • Through the resolvers these requests returned 404 before. So in practice this only affects code that reads event.path directly: data-class users (@event_source(data_class=VPCLatticeEvent) / VPCLatticeEventV2), catch-all routes, not_found handlers and middlewares.

Follow-ups (not in this PR). These have a different root cause: V2 sends every header and query string value as a list.

  • aws_lambda_powertools/event_handler/middlewares/openapi_validation.py:144 calls .strip() on headers.get("content-type", ""), which is a list for V2. With enable_validation=True and a body parameter, a real V2 event fails with AttributeError: 'list' object has no attribute 'strip'.
  • VpcLatticeV2Model declares headers and queryStringParameters as dict[str, str], so the parser rejects real V2 events. TypeScript fixed the same schema in powertools-lambda-typescript#5764 and chore(ci): new pre-release 3.3.1a21 #5773.
  • The docs sample payloads (examples/event_handler_rest/src/getting_started_vpclattice*_resolver.json, examples/event_sources/events/vpc_lattice*_payload.json) also leave the query string out of the path.

AI assistance: prepared with Claude Code; the change was reviewed and the tests were run locally.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.

🤖 Generated with Claude Code

VPC Lattice sends the query string inside the request path (V1 raw_path,
V2 path) as well as parsed. VPCLatticeEvent.path and VPCLatticeEventV2.path
returned that value verbatim, so a request like /echo?QS1=value1 never
matched the anchored route for /echo and got a 404 on both resolvers. The
same value broke strip_prefixes and Swagger's ?format=json.

Strip the query in the data classes so path is the URL path without the
query string, as for the other BaseProxyEvent sources. Split on the first
"?" (RFC 3986), so an encoded %3F stays in the path. V1 raw_path and the
raw event keep the original value.
@AtulJoshi1206
AtulJoshi1206 requested a review from a team as a code owner October 17, 2026 05:18
@AtulJoshi1206
AtulJoshi1206 requested a review from svozza October 17, 2026 05:18
@boring-cyborg boring-cyborg Bot added the tests label Oct 17, 2026
@powertools-for-aws-oss-automation powertools-for-aws-oss-automation Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Oct 17, 2026

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks! I reproduced the 404 with the V2 event from the TS issue and it passes with this fix. AWS docs confirm the path carries the query string in both versions, so this is correct. Approving.

@sonarqubecloud

Copy link
Copy Markdown

@codecov

codecov Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.93%. Comparing base (f58cd87) to head (85113ae).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #8572   +/-   ##
========================================
  Coverage    96.93%   96.93%           
========================================
  Files          318      318           
  Lines        15999    16002    +3     
  Branches      1419     1419           
========================================
+ Hits         15508    15511    +3     
  Misses         349      349           
  Partials       142      142           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@leandrodamascena
leandrodamascena merged commit 93e577c into aws-powertools:develop Oct 17, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L Denotes a PR that changes 100-499 lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: VPC Lattice requests with a query string return 404 in VPCLatticeResolver and VPCLatticeV2Resolver

2 participants