Skip to content

Fix ClassCastException when applying HandlerAspect to routes with path parameters#3920

Open
Godzilla675 wants to merge 19 commits intozio:mainfrom
Godzilla675:DEV
Open

Fix ClassCastException when applying HandlerAspect to routes with path parameters#3920
Godzilla675 wants to merge 19 commits intozio:mainfrom
Godzilla675:DEV

Conversation

@Godzilla675
Copy link

/claim #3141

Summary

This PR fixes the ClassCastException that occurs when combining a route with path parameters and a HandlerAspect middleware.

Changes

Root Cause

The Handler.@@ operator assumes input is Request, but applied to a parameterized route, the input is a Tuple.
The fix applies the aspect at the Route level where we can handle this correctly.

…h parameters (zio#3141)

Added @@ operators to Route trait that apply aspects after path parameter decoding, avoiding the type mismatch that caused the ClassCastException.
@netlify
Copy link

netlify bot commented Jan 23, 2026

Deploy Preview for zio-http ready!

Name Link
🔨 Latest commit 92263d0
🔍 Latest deploy log https://app.netlify.com/projects/zio-http/deploys/69ae08dd590bef0008bfa4d0
😎 Deploy Preview https://deploy-preview-3920--zio-http.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a ClassCastException (issue #3141) that occurs when combining a HandlerAspect middleware with a route that has path parameters. The root cause was that Handler.@@ assumes the handler's input is Request, but when applied directly to a handler used in a parameterized route, the input is actually a tuple of (params, Request). The fix adds @@ operators directly on the Route trait, which applies the aspect via Route.transform — operating on the handler after path parameters have been decoded, when the handler's input type is guaranteed to be Request.

Changes:

  • Added three @@ operator overloads to the Route trait for Middleware, HandlerAspect[_, Unit], and HandlerAspect[_, Ctx], mirroring the existing pattern on Routes
  • Added a regression test in RouteSpec verifying that a HandlerAspect can be applied to a route with path parameters without throwing a ClassCastException

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
zio-http/shared/src/main/scala/zio/http/Route.scala Adds three @@ operator overloads to the Route sealed trait for applying Middleware and HandlerAspect at the route level
zio-http/jvm/src/test/scala/zio/http/RouteSpec.scala Adds a regression test for issue #3141, verifying route + path params + HandlerAspect works without ClassCastException

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +378 to +401
suite("HandlerAspect with path parameters")(
test("HandlerAspect should work with routes containing path parameters (#3141)") {
val authAspect: HandlerAspect[Any, Int] =
HandlerAspect.interceptIncomingHandler(Handler.fromFunction[Request] { request =>
(request, 42)
})

// Fixed: Apply aspect to the Route (not the Handler) using the new Route.@@ operator
// This correctly applies the aspect after path parameters are decoded
val route = (Method.GET / "base" / string("id") ->
handler((id: String, req: Request) => {
ZIO.succeed(Response.text(s"id=$id"))
})) @@ authAspect

val routes = Routes(route)

for {
response <- routes.runZIO(Request.get(url"/base/test123"))
body <- response.body.asString
} yield assertTrue(
response.status == Status.Ok,
body == "id=test123",
)
},
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

This test verifies the ClassCastException from #3141 no longer occurs, but it doesn't verify that the context provided by the aspect (value 42) is actually accessible within the handler. Consider adding an assertion that the context is propagated correctly, e.g., by using withContext in the handler to access the Int context and including its value in the response body. This would more closely match the original issue's reproducer, which uses withContext.

Copilot uses AI. Check for mistakes.
Comment on lines +393 to +413
/**
* Applies a middleware aspect to this route.
*/
final def @@[Env1 <: Env](aspect: Middleware[Env1]): Route[Env1, Err] =
aspect(self.toRoutes).routes.head.asInstanceOf[Route[Env1, Err]]

/**
* Applies a handler aspect that does not provide context to this route.
*/
final def @@[Env0](aspect: HandlerAspect[Env0, Unit]): Route[Env with Env0, Err] =
self.transform[Env with Env0](handler => handler @@ aspect)

/**
* Applies a handler aspect that provides context to this route. The aspect is
* applied after path parameters are decoded, so the handler receives a plain
* Request rather than a tuple of (params, request).
*/
final def @@[Env0, Ctx <: Env](aspect: HandlerAspect[Env0, Ctx])(implicit
tag: Tag[Ctx],
): Route[Env0, Err] =
self.transform[Env0](handler => handler @@ aspect)
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

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

The Routes class provides a 4th @@ overload: def @@[Env0]: ApplyContextAspect[Env, Err, Env0] (see Routes.scala:55-56), which allows explicit specification of the remaining environment type after context elimination (used as routes.@@[Int & Long](authContext) in the existing test at line 355). This overload is missing from Route, which means users with intersection type environments who need partial context elimination would have to first wrap their route in Routes. Consider adding an equivalent ApplyContextAspect overload to Route for API consistency with Routes.

Copilot uses AI. Check for mistakes.
Godzilla675 and others added 2 commits March 8, 2026 23:35
- Replace broken [[zio.http.Routes.ApplyContextAspect]] Scaladoc link
  with backtick-quoted text to fix scaladoc generation failure
- Update tests to use withContext to access Int context value,
  matching the original issue zio#3141 reproducer pattern

Co-authored-by: Godzilla675 <131464726+Godzilla675@users.noreply.github.com>
Fix Scaladoc link error and use withContext in tests
@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ Godzilla675
❌ Copilot
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants