Skip to content

fix: url-decode path segments in route layer#223

Merged
yaythomas merged 1 commit into
mainfrom
fix/decode-durable-execution-arn-in-routes
May 21, 2026
Merged

fix: url-decode path segments in route layer#223
yaythomas merged 1 commit into
mainfrom
fix/decode-durable-execution-arn-in-routes

Conversation

@yaythomas
Copy link
Copy Markdown
Contributor

@yaythomas yaythomas commented May 21, 2026

Issue #, if available: #222

Description of changes:

dex-local-runner (WebRunner) returned 404 for every Get/State/History/Checkpoint/Stop call against an execution whose ARN contained a literal /, and silently returned an empty list for ListDurableExecutionsByFunction when the function name contained : or $. boto's rest-json serializer percent-encodes those characters in non-greedy URI labels (e.g. /%2F, :%3A, $%24), but the WebServer's route layer wasn't decoding the captured segment before using it as a store-lookup key.

This PR centralizes URL-decoding at the parser layer so every captured path segment inherits the behavior:

  • src/.../web/routes.py — decode each segment in Route.from_string. raw_path is preserved as the original wire path for logging. Order matters: split on the literal / first so %2F-encoded slashes inside captured values stay inside their segment instead of acting as path separators. The three callback routes drop their now-redundant per-callsite unquote() calls (added in fix: decode callback id in routes #117 for the same bug shape).
  • tests/web/routes_test.py — strengthen test_route_with_special_characters to assert both segments[N] and the named field are decoded, while raw_path keeps the wire form.
  • tests/web/e2e/routes_arn_encoding_int_test.py (new) — drive a real boto3 Lambda client against a live WebServer for every affected operation, with values containing the characters boto percent-encodes. Closes the test-coverage gap that let the bug ship: no existing test ran realistic boto serialization through the route layer.

Affected runners

  • WebRunner / dex-local-runner — fixed.
  • DurableFunctionTestRunner (in-process) — unaffected; uses InMemoryServiceClient which ignores durable_execution_arn.
  • DurableFunctionCloudTestRunner — unaffected; ARNs come from the real Lambda backend.

Verification

  • hatch test — 1245 passed (1239 prior + 6 new regression tests).
  • hatch fmt --check — clean.
  • hatch run types:check — clean.
  • Pre-fix sanity: stashing routes.py causes 7 tests (6 integration + the strengthened unit test) to fail with %XX-in-error or wire-form-inequality messages, confirming the regression coverage is real.

Closes #222.

Dependencies

If this PR requires testing against a specific branch of the Python Language SDK (e.g., for unreleased changes), uncomment and specify the branch below. Otherwise, leave commented to use the main branch.

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

After PR #216, durable-execution ARNs minted by Execution.new()
contain a literal '/' of the form "<uuid>/<invocation-id>". boto's
rest-json serializer percent-encodes '/' as %2F in the non-greedy
{DurableExecutionArn} URI label, so paths arriving at the local
WebServer look like:

    /2025-12-01/durable-executions/<uuid>%2F<invocation-id>

The same shape applies to ListDurableExecutionsByFunction with
function names like "MyFunction:$LATEST" (':' -> %3A, '$' -> %24).
Without decoding, store lookups never match the key and every
Get/State/History/Checkpoint/Stop returns 404. List queries silently
return an empty result set.

- Decode each segment once in Route.from_string. raw_path is kept
  as the original wire string for logging. Splitting on '/' happens
  before decoding so a captured value containing %2F stays inside
  its segment instead of acting as a path separator.
- Remove the now-redundant per-route unquote() calls from the three
  callback routes (added in #117 for the same bug shape).
- Add a real-boto regression test under tests/web/e2e/ that drives
  a live WebServer for every affected operation with values containing
  the characters boto percent-encodes. Closes the test-coverage gap
  that let the bug ship.
- Strengthen test_route_with_special_characters to assert both
  segments[N] and the named field are decoded while raw_path keeps
  the wire form.

Affects users running WebRunner / dex-local-runner against their
durable function in RIE; pre-fix, the function 404s on its first
checkpoint after upgrading to 1.2.0.

Closes #222
@yaythomas
Copy link
Copy Markdown
Contributor Author

some local-runner tests I ran:

Unfixed (origin/main / shipped v1.2.0):

get_durable_execution FAIL: 404 Execution %2Finv-unfixed not found
get_durable_execution_state FAIL: 404 Execution %2Finv-unfixed not found
get_durable_execution_history FAIL: 404 Execution %2Finv-unfixed not found
stop_durable_execution FAIL: 404 Execution %2Finv-unfixed not found

Fixed (this PR):

get_durable_execution OK (Status=RUNNING, ARN echoes back)
get_durable_execution_state OK (returns Operations)
get_durable_execution_history OK (returns 1 event)
stop_durable_execution OK

@yaythomas yaythomas merged commit fb8e34c into main May 21, 2026
7 checks passed
@yaythomas yaythomas deleted the fix/decode-durable-execution-arn-in-routes branch May 21, 2026 17:54
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.

[Bug]: WebServer route layer doesn't URL-decode path segments

2 participants