Skip to content

Commit

Permalink
Allow applying aspects only to handlers with Request input (zio#3141)
Browse files Browse the repository at this point in the history
  • Loading branch information
987Nabil committed Sep 20, 2024
1 parent 46980fd commit 959a895
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 10 deletions.
2 changes: 1 addition & 1 deletion docs/reference/aop/handler_aspect.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ We notice in the response that first `basicAuth` handler aspect responded `HTTP/
We can abort the requests by specific response using `HandlerAspect.fail` and `HandlerAspect.failWith` aspects, so the downstream handlers will not be executed:

```scala mdoc:invisible
val myHandler = Handler.identity
val myHandler = Handler.ok
```

```scala mdoc:compile-only
Expand Down
21 changes: 21 additions & 0 deletions zio-http/jvm/src/test/scala/zio/http/RouteSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package zio.http

import scala.annotation.nowarn

import zio._
import zio.test._

Expand Down Expand Up @@ -209,5 +211,24 @@ object RouteSpec extends ZIOHttpSpec {
} yield assertTrue(bodyString == expected)
},
),
test("Apply context middleware to route with path parameter") {
case class WebSession(id: Int)

def maybeWebSession: HandlerAspect[Any, Option[WebSession]] =
HandlerAspect.interceptIncomingHandler(
Handler.fromFunctionZIO[Request] { req =>
ZIO.succeed((req, None))
},
)

val route = (Method.GET / "base" / string("1") -> handler((_: String, _: Request) => {
withContext((_: Option[WebSession]) => {
ZIO.logInfo("Hello").as(Response.ok)
})
})) @@ maybeWebSession
route(Request.get(url"/base/1")).map { response =>
assertTrue(extractStatus(response) == Status.Ok)
}
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import zio._
trait HandlerVersionSpecific {
private[http] class ApplyContextAspect[-Env, +Err, -In, +Out, Env0](private val self: Handler[Env, Err, In, Out]) {
def apply[Env1, Ctx: Tag, In1 <: In](aspect: HandlerAspect[Env1, Ctx])(implicit
in: Handler.IsRequest[In1],
ev0: Request <:< In,
ev: Env0 with Ctx <:< Env,
out: Out <:< Response,
err: Err <:< Response,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package zio.http

import zio.Tag

trait RouteCompanionVersionSpecific {
private[http] class ApplyContextAspect[-Env, +Err, Env0](private val self: Route[Env, Err]) {
def apply[Env1, Env2 <: Env, Ctx: Tag](aspect: HandlerAspect[Env1, Ctx])(implicit
ev: Env0 with Ctx <:< Env,
): Route[Env0 with Env1, Err] = self.transform(_.@@[Env0](aspect))
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import zio.*
trait HandlerVersionSpecific {
private[http] class ApplyContextAspect[-Env, +Err, -In, +Out, Env0](self: Handler[Env, Err, In, Out]) {
transparent inline def apply[Env1, Ctx, In1 <: In](aspect: HandlerAspect[Env1, Ctx])(implicit
in: Handler.IsRequest[In1],
ev0: Request <:< In,
ev: Env0 with Ctx <:< Env,
out: Out <:< Response,
err: Err <:< Response,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package zio.http

trait RouteCompanionVersionSpecific {
private[http] class ApplyContextAspect[-Env, +Err, Env0](private val self: Route[Env, Err]) {
transparent inline def apply[Env1, Env2 <: Env, Ctx](aspect: HandlerAspect[Env1, Ctx])(implicit
ev: Env0 with Ctx <:< Env,
): Route[Env0 with Env1, Err] = self.transform(_.@@[Env0](aspect))
}

}
6 changes: 3 additions & 3 deletions zio-http/shared/src/main/scala/zio/http/Handler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import zio.http.template._
sealed trait Handler[-R, +Err, -In, +Out] { self =>

def @@[Env1 <: R, In1 <: In](aspect: HandlerAspect[Env1, Unit])(implicit
in: Handler.IsRequest[In1],
ev: Request <:< In,
out: Out <:< Response,
err: Err <:< Response,
): Handler[Env1, Response, Request, Response] = {
Expand All @@ -46,7 +46,7 @@ sealed trait Handler[-R, +Err, -In, +Out] { self =>
}

def @@[Env0, Ctx <: R, In1 <: In](aspect: HandlerAspect[Env0, Ctx])(implicit
in: Handler.IsRequest[In1],
ev: Request <:< In,
out: Out <:< Response,
err: Err <:< Response,
trace: Trace,
Expand Down Expand Up @@ -981,7 +981,7 @@ object Handler extends HandlerPlatformSpecific with HandlerVersionSpecific {
fromResponse(Response.html(view))

/**
* Creates a pass thru Handler instance
* Creates a pass through Handler instance
*/
def identity[A]: Handler[Any, Nothing, A, A] =
new Handler[Any, Nothing, A, A] {
Expand Down
20 changes: 20 additions & 0 deletions zio-http/shared/src/main/scala/zio/http/HandlerAspect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,26 @@ final case class HandlerAspect[-Env, +CtxOut](
}
}

/**
* Applies middleware to the specified handler, which may ignore the context
* produced by this middleware.
*/
override def apply[Env1 <: Env, Err](
routes: Route[Env1, Err],
): Route[Env1, Err] =
routes.transform[Env1] { handler =>
if (self == HandlerAspect.identity) handler
else {
for {
tuple <- protocol.incomingHandler
(state, (request, ctxOut)) = tuple
either <- Handler.fromZIO(handler(request)).either
response <- Handler.fromZIO(protocol.outgoingHandler((state, either.merge)))
response <- if (either.isLeft) Handler.fail(response) else Handler.succeed(response)
} yield response
}
}

/**
* Applies middleware to the specified handler, which must process the context
* produced by this middleware.
Expand Down
3 changes: 3 additions & 0 deletions zio-http/shared/src/main/scala/zio/http/Middleware.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ trait Middleware[-UpperEnv] { self =>

def apply[Env1 <: UpperEnv, Err](routes: Routes[Env1, Err]): Routes[Env1, Err]

def apply[Env1 <: UpperEnv, Err](route: Route[Env1, Err]): Route[Env1, Err] =
(route.toRoutes @@ self).routes.head

def @@[UpperEnv1 <: UpperEnv](
that: Middleware[UpperEnv1],
): Middleware[UpperEnv1] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,24 @@ import zio.Zippable

@implicitNotFound("""
Your request handler is required to accept both parameters ${A}, as well as the incoming [[zio.http.Request]].
This is true even if you wish to ignore some parameters or the request itself. Try to add missing parameters
until you no longer receive this error message. If all else fails, you can construct a handler manually using
This is true even if you wish to ignore some parameters or the request itself. Try to add missing parameters
until you no longer receive this error message. If all else fails, you can construct a handler manually using
the constructors in the companion object of [[zio.http.Handler]] using the precise types.""")
final class RequestHandlerInput[A, I](val zippable: Zippable.Out[A, Request, I])
object RequestHandlerInput {
implicit def apply[A, I](implicit zippable: Zippable.Out[A, Request, I]): RequestHandlerInput[A, I] =
new RequestHandlerInput(zippable)
}

@implicitNotFound("""
Your request handler is required to accept both parameters ${A}, as well as the incoming [[zio.http.Request]].
This is true even if you wish to ignore some parameters or the request itself. Try to add missing parameters
until you no longer receive this error message. If all else fails, you can construct a handler manually using
the constructors in the companion object of [[zio.http.Handler]] using the precise types.""")
final class RequestHandlerInputWithContext[A, I, Ctx](val zippable: Zippable.Out[A, (Ctx, Request), I])
object RequestHandlerInputWithContext {
implicit def apply[A, I, Ctx](implicit
zippable: Zippable.Out[A, (Ctx, Request), I],
): RequestHandlerInputWithContext[A, I, Ctx] =
new RequestHandlerInputWithContext(zippable)
}
18 changes: 16 additions & 2 deletions zio-http/shared/src/main/scala/zio/http/Route.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,21 @@ import zio.http.codec.PathCodec
* Individual routes can be aggregated using [[zio.http.Routes]].
*/
sealed trait Route[-Env, +Err] { self =>
import Route.{Augmented, Handled, Provided, Unhandled}
import Route._

def @@[Env1 <: Env](aspect: Middleware[Env1]): Route[Env1, Err] =
aspect(self)

def @@[Env0](aspect: HandlerAspect[Env0, Unit]): Route[Env with Env0, Err] =
aspect(self)

def @@[Env0, Ctx <: Env](
aspect: HandlerAspect[Env0, Ctx],
)(implicit tag: Tag[Ctx]): Route[Env0, Err] =
self.transform(_ @@ aspect)

def @@[Env0]: ApplyContextAspect[Env, Err, Env0] =
new ApplyContextAspect[Env, Err, Env0](self)

/**
* Applies the route to the specified request. The route must be defined for
Expand Down Expand Up @@ -332,7 +346,7 @@ sealed trait Route[-Env, +Err] { self =>
): Route[Env1, Err] =
Route.Augmented(self, f)
}
object Route {
object Route extends RouteCompanionVersionSpecific {

def handledIgnoreParams[Env](
routePattern: RoutePattern[_],
Expand Down

0 comments on commit 959a895

Please sign in to comment.