Skip to content

Match a trailing wildcard in module HTTP route paths - #6155

Open
paoValle wants to merge 1 commit into
clockworklabs:masterfrom
paoValle:feat/6133-http-route-wildcard
Open

paoValle wants to merge 1 commit into
clockworklabs:masterfrom
paoValle:feat/6133-http-route-wildcard

Conversation

@paoValle

@paoValle paoValle commented Oct 13, 2026 •

Copy link
Copy Markdown

Description of Changes

Part of #6133 — this slice adds the trailing wildcard; the :param part of the issue stays open.

A route path may now end with a single *, which matches any remaining path:

  • the matcher (ModuleDef::match_http_route) matches a wildcard route against any request path starting with the prefix before the *: /webhooks/* serves /webhooks/github, /webhooks/ and /webhooks/a/b, but not /webhooks;
  • the publish-time validator rejects * anywhere but the end, and rejects a wildcard route together with a route it would match for the same HTTP method — GET /webhooks/* plus POST /webhooks/github is accepted, GET /webhooks/* plus GET /webhooks/github is not — so no request can be claimed by two routes;
  • the predicates live once per language: spacetimedb_lib::http for the schema validator and the Rust module router, and the same logic in the TypeScript, C# and C++ routers so a client-side check does not reject a path the host accepts;
  • the HTTP-handlers page documents the wildcard and what the handler receives.

API and ABI breaking changes

None. A route path is still a string and * was rejected before this change, so no module that publishes today changes behaviour, and the request/response ABI is untouched.

Rollback safety impact

n/a — no ControlDB table, reducer, system table or on-disk format is written, cleared or changed. A module published with a wildcard route while this is deployed would, after a rollback, simply stop matching that route (the old matcher compares paths exactly) and the request would 404; nothing is stored or migrated.

Expected complexity level and risk

2. The change is a handful of pure predicates plus one branch in the matcher. What lifts it above trivial is that the same logic exists in four places (Rust validator, Rust router, TypeScript router, C#/C++ routers) and the interesting part is where they could disagree, which is why the predicates take the same shapes and the tests pin the same cases in Rust and in TypeScript.

Known, not touched here: nest/Nest still refuses when an existing route's path starts with the nested prefix, which is stricter than the routing rule and now reachable with a wildcard (Get("/webhooks/*").Nest("/webhooks/", …)). That strictness predates this change and is identical in all three SDKs.

Testing

What I ran:

  • cargo test -p spacetimedb-lib --lib → 26 passed, 0 failed
  • cargo test -p spacetimedb-schema --lib → 199 passed, 0 failed (11 of them the http_routes_* cases, 6 of them new)
  • cargo check -p spacetimedb --features unstable → clean; cargo fmt --check → clean
  • vitest run tests/http_handlers.test.ts → 25 passed, 0 failed (3 new cases)
  • mutation checks: reverting the overlap-aware comparison and the *-position check makes 3 new schema tests fail; reverting the matcher's route_path_matches makes the matcher test fail; reverting the TypeScript predicates makes the 3 new TypeScript tests fail.

The C# and C++ ports, which are the parts CI would otherwise be the first to compile:

  • crates/bindings-csharp/Runtime compiles clean under dotnet test -warnaserror (net8.0), and the router tests pass — 10 passed, 0 failed — including the three added here.
    I ran that in a copy of crates/bindings-csharp because on this machine (macOS arm64) the net10.0 target cannot restore: it references Microsoft.DotNet.ILCompiler.LLVM 10.0.0-*, which is not published for osx-arm64. Two things to know about that copy, both pre-existing: net10.0 was removed from TargetFrameworks, and Runtime.Tests/JwtClaimsTest.cs was excluded because it does not compile (error CS1729: 'JwtClaims' does not contain a constructor that takes 2 arguments) — which is very likely why Runtime.Tests is not in SpacetimeSharpSATS.sln and therefore not built by the csharp_tests job. That is a separate gap and I did not touch it.
  • clang++ -std=c++20 -fsyntax-only -I crates/bindings-cpp/include on a translation unit including <spacetimedb/router.h> is clean (only the pre-existing import_module/import_name attribute warnings from abi/abi.h on a non-wasm target). The C++ predicates were verified by reading against the Rust ones, not by a test run: crates/bindings-cpp/tests has no router tests.

Still open, and a reviewer may want them:

  • crates/bindings-csharp/Runtime.Tests is not in SpacetimeSharpSATS.sln, so the tests I added there are not run by the csharp_tests job. Adding the project to the solution (after the JwtClaimsTest.cs breakage is fixed) is a separate change and I did not make it here.
  • crates/smoketests/tests/cluster/http_routes.rs has no wildcard case, so the axum route/*path capture into the matcher is covered only by the unit tests. Happy to add a wildcard module to the smoketests if you want the end-to-end path in this PR.

@CLAassistant

CLAassistant commented Oct 13, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

A route path may now end with a single `*`, which matches any remaining path, so a
webhook receiver can register `/webhooks/*` instead of one route per source.

The matcher, the publish-time validation and the four SDK routers each share one set
of predicates, so a pattern one of them accepts is one the others accept. Validation
rejects `*` anywhere but the end, and rejects a wildcard route together with a route
it would match for the same HTTP method, rather than letting one silently shadow the
other.
@paoValle
paoValle force-pushed the feat/6133-http-route-wildcard branch from 5d987c8 to 58a850d Compare October 17, 2026 10:01
@paoValle

Copy link
Copy Markdown
Author

Rebased on master (7cdacda31, 27 commits). One conflict, in crates/schema/src/def.rs: kept upstream's hash_bytes import and added spacetimedb_lib::http::route_path_matches alongside it. Everything else applied cleanly.

Re-ran on the rebased tree:

  • cargo test -p spacetimedb-lib --lib → 26 passed
  • cargo test -p spacetimedb-schema --lib → 203 passed
  • cargo test -p spacetimedb-schema --test ensure_same_schema → 5 passed, 5 ignored (note: this suite needs pnpm -s build in crates/bindings-typescript first, or it fails on an unresolved spacetimedb/server — that was my first run, not a real failure)
  • cargo check -p spacetimedb --features unstable → ok, cargo fmt --check → clean
  • npx vitest run tests/http_handlers.test.ts → 25 passed
  • C++ port: clang++ -std=c++20 -fsyntax-only -DSPACETIMEDB_UNSTABLE_FEATURES -I include on a TU including spacetimedb/router.h → clean (only the pre-existing wasm-attribute warnings)

The C# port applied cleanly as well; Runtime.Tests cannot restore on osx-arm64 (Microsoft.DotNet.ILCompiler.LLVM 10.0.0 is not published for that RID), so it is covered by the previous 10/10 run and not re-run here, same as before.

This branch has not been deployed

No deployments
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.

3 participants