Skip to content

refactor(core): make RouteHandler::handle a native async method - #156

Open
alukach wants to merge 2 commits into
mainfrom
refactor/route-handler-erasure
Open

alukach wants to merge 2 commits into
mainfrom
refactor/route-handler-erasure

Conversation

@alukach

@alukach alukach commented Sep 25, 2026

Copy link
Copy Markdown
Member

What I'm changing

RouteHandler was the odd one out among the core traits. Middleware, ProxyBackend, and the registries all use native return-position impl Future methods, and Middleware hides the object-safety boxing behind an internal ErasedMiddleware blanket impl. RouteHandler::handle instead returned a public RouteHandlerFuture = Pin<Box<dyn Future ..>>, so every implementor (OIDC discovery, JWKS, STS, and anyone writing a health check) had to write Box::pin(async move { .. }) by hand and handle the early-return case with return Box::pin(async { None }).

This PR applies the Middleware pattern to RouteHandler. Implementors write async fn handle, and the router boxes internally.

How I did it

  • crates/core/src/route_handler.rs: RouteHandler::handle now returns impl Future<Output = Option<ProxyResult>> + MaybeSend + 'a. A new pub(crate) trait ErasedRouteHandler with a blanket impl<T: RouteHandler> does the Box::pin. RouteHandlerFuture becomes pub(crate); it was only ever useful for satisfying the old signature. The trait doc example shows the async fn form and links to Middleware as the precedent.
  • crates/core/src/router.rs: stores Box<dyn ErasedRouteHandler>. The route() signature is unchanged (impl RouteHandler + 'static), so callers are unaffected. Adds a unit test with a HealthCheck handler written exactly as an integrator would, checking dispatch, decline (None), and no-match.
  • crates/oidc-provider/src/route_handler.rs, crates/sts/src/route_handler.rs: the three handlers become async fn bodies; the STS handler's try_handle_sts(..).await? now uses ? on Option directly inside the async fn.
  • docs/architecture/request-lifecycle.md: the "Method routing" section described get/post/put hooks that the trait never had and showed Box::pin. Replaced with an "Implementing a handler" section matching the real API.

This is a breaking change for external RouteHandler implementors: drop the Box::pin and change fn handle(..) -> RouteHandlerFuture<'a> to async fn handle(..) -> Option<ProxyResult>.

Test plan

  • cargo test — 293 passed (new router::tests::async_fn_handler_dispatches_and_falls_through; STS route-handler tests exercise the erased path)
  • cargo check --all-targets
  • cargo check -p multistore-cf-workers --target wasm32-unknown-unknown (the MaybeSend no-op path)
  • cargo check -p multistore-cf-workers-example --target wasm32-unknown-unknown
  • cargo clippy -- -D warnings
  • cargo fmt --check

🤖 Generated with Claude Code

`RouteHandler` was the one core trait that exposed its boxing: `handle`
returned a public `RouteHandlerFuture` alias, so every implementor wrote
`Box::pin(async move { .. })` by hand, while `Middleware` hides the same
boxing behind an internal erased trait. Both traits now work the same way.

`handle` returns `impl Future + MaybeSend`, implementors write `async fn`,
and a `pub(crate) ErasedRouteHandler` blanket impl boxes the future for
`Router`'s `Box<dyn ..>` storage. `RouteHandlerFuture` becomes crate-private.
The OIDC and STS handlers drop their `Box::pin` wrappers, and the
request-lifecycle doc replaces a stale example that referenced per-method
hooks (`get`, `post`) the trait never had.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@claude

claude Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Claude finished @alukach's task in 14s —— View job


✅ No blocking issues — safe to merge.

I read the full diff and found no correctness or security problems.

  • Erasure: The blanket impl<T: RouteHandler> ErasedRouteHandler for T boxes the future. RouteHandlerFuture keeps the Send bound on native targets and drops it on wasm32. That matches the MaybeSend bound on the trait method.
  • Dispatch: Router stores Box<dyn ErasedRouteHandler>. The route(impl RouteHandler + 'static) signature is unchanged, so existing callers are unaffected.
  • Handler ports: The OIDC and STS ports keep their behavior. ? on Option inside the async fn returns None, which is the same early-return as before.
  • Docs: The docs now describe the real API. The old get/post hook text is gone.
  • Test: The new HealthCheck test covers dispatch, decline and no-match.
  • Not run: I did not run the build or tests here. I'm relying on your test-plan results.

Simplify (ponytail)

  • crates/core/src/route_handler.rs:184-190: the two cfg-gated RouteHandlerFuture aliases could be one. Use Pin<Box<dyn Future<Output = Option<ProxyResult>> + MaybeSend + 'a>>, assuming MaybeSend is usable as a dyn bound. On native it is a blanket trait over Send, and on wasm a no-op. If it isn't usable as a dyn bound, ignore this. The alias is only used in the one erased trait, so you could also inline it there and delete the alias.

💰 Estimated review cost: $0.12 · 0m13s · 4 turns

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

📖 Docs preview deployed to https://multistore-docs-pr-156.development-seed.workers.dev

  • Date: 2026-10-01T17:05:58Z
  • Commit: dceffbc

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

🚀 Latest commit deployed to https://multistore-proxy-pr-156.development-seed.workers.dev

  • Date: 2026-10-01T17:05:58Z
  • Commit: dceffbc

@alukach
alukach marked this pull request as ready for review October 1, 2026 17:03
Resolve conflict in crates/sts/src/route_handler.rs: keep the async fn
form of StsHandler::handle and carry over main's GetCallerIdentity
dispatch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
preview — 70df8285 Deployed Oct 1, 2026 by alukach via Deploy & Test / Deploy #474
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