Skip to content

fix(lite): return helpful BadPath error for unencoded '/' in stream names - #704

Open
vjymisal0 wants to merge 1 commit into
s2-streamstore:mainfrom
vjymisal0:fix/stream-slash-badpath
Open

fix(lite): return helpful BadPath error for unencoded '/' in stream names#704
vjymisal0 wants to merge 1 commit into
s2-streamstore:mainfrom
vjymisal0:fix/stream-slash-badpath

Conversation

@vjymisal0

Copy link
Copy Markdown

Summary

Closes #635.

Stream names may legitimately contain / (e.g. cdc/products), but a REST request that forgets to percent-encode it (/v1/streams/cdc/products/records instead of /v1/streams/cdc%2Fproducts/records) resolves to more path segments than any registered route expects. axum's default router rejects that as a bare, bodyless 404 — a head-scratcher, since an encoded-but-nonexistent stream correctly returns a JSON stream_not_found body.

Fix

Added a fallback route in lite/src/handlers/v1/streams.rs:

.route("/streams/{stream}/{*rest}", axum::routing::any(ambiguous_stream_path))

This is scoped to one segment past {stream} rather than /streams/{*rest} directly — registering the wildcard at the same depth as the existing /streams/{stream} route panics at router-build time with a matchit conflict (confirmed locally; see Tests). Scoping it one level deeper lets it coexist: literal children like .../records and .../records/tail still win over the wildcard (more specific routes take priority in matchit), and the bare /streams/{stream} route is untouched since the wildcard requires at least one more segment.

Added ServiceError::AmbiguousStreamPath, mapped to the existing ErrorCode::BadPath (400), with a message pointing at percent-encoding — matching the issue's first suggested fix option. I left the second option (documenting the %2F requirement in the OpenAPI spec / protocol docs) out of this PR to keep the diff focused on the routing behavior; happy to follow up on the docs separately if useful.

Tests

lite/src/handlers/v1/streams.rs, new mod tests (following the existing pattern in records.rs):

  • RED (would fail before the fix, confirmed locally — see below): unencoded_slash_in_stream_name_returns_helpful_bad_path_error — hits /v1/streams/cdc/products/records and asserts a 400/bad_path JSON body containing "percent-encoded".
  • GREEN companion: percent_encoded_slash_in_stream_name_reaches_the_records_route — hits /v1/streams/cdc%2Fproducts/records and asserts the response is a normal (non-bad_path) JSON error from the records handler, not the bare unmatched-route 404.

Both pass locally. Note on the RED step specifically: my first attempt registered the wildcard at /streams/{*rest} (matching the issue's exact suggested path), which panics with Insertion failed due to conflict with previously registered route: /streams/{stream} — that's what pushed me to the one-segment-deeper scoping above.

Verification

  • cargo test -p s2-lite --lib handlers::v1::streams — 2 passed.
  • cargo clippy -p s2-lite --all-features --lib -- -D warnings --allow deprecated — clean.
  • cargo +nightly fmt -p s2-lite — clean.

Ran all three against a local, uncommitted patch to lite/Cargo.toml that disables the tikv-jemallocator dependency on the GNU target — my local Windows toolchain can't link it (unrelated mingw32-make failure building jemalloc from source, and separately the MSVC toolchain here fails at the link step on completely unrelated crates like quote/getrandom, before reaching this crate at all — looks like an incomplete local VS Build Tools linker setup, not anything about this change). That Cargo.toml edit is not part of this diff — confirmed with git diff --stat showing only the two files above.

🤖 Generated with the help of Claude Code, reviewed and tested by me before opening.

…ames

Stream names may legitimately contain '/' (e.g. "cdc/products"), but
callers who forget to percent-encode it hit a bare, bodyless 404 from
axum's default unmatched-route handler instead of a diagnosable error.

Add a fallback route scoped to one segment past /streams/{stream} that
returns a BadPath error explaining the fix, without shadowing the more
specific /streams/{stream} and /streams/{stream}/records* routes.

Closes s2-streamstore#635

Signed-off-by: vjymisal0 <vjymisal0@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 18, 2026 11:56

@claude claude 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.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@greptile-apps

greptile-apps Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

PR author is not in the allowed authors list.

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@vjymisal0

Copy link
Copy Markdown
Author

PR author is not in the allowed authors list.

what do you mean?

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.

REST: stream names containing / must be percent-encoded, and the unencoded 404 gives no hint

2 participants