Skip to content

Commit

Permalink
Resolve Routes environment via context providing middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
987Nabil committed Oct 31, 2023
1 parent 88730b0 commit 3fc353e
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 17 deletions.
41 changes: 28 additions & 13 deletions zio-http/src/main/scala/zio/http/HandlerAspect.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import java.nio.charset._
import zio._
import zio.metrics._

import zio.http.endpoint.EndpointMiddleware.None.Err
import zio.http.template._

/**
Expand All @@ -42,7 +43,7 @@ import zio.http.template._
* specialized to entuple contexts, so that each layer may only add context to
* the contextual output.
*/
final case class HandlerAspect[-Env, +CtxOut](
final case class HandlerAspect[-Env, +CtxOut: Tag](
protocol: ProtocolStack[
Env,
Request,
Expand All @@ -59,7 +60,7 @@ final case class HandlerAspect[-Env, +CtxOut](
* incoming requests, and first on outgoing responses. Context from both
* middleware will be combined using tuples.
*/
def ++[Env1 <: Env, CtxOut2](
def ++[Env1 <: Env, CtxOut2: Tag](
that: HandlerAspect[Env1, CtxOut2],
)(implicit zippable: Zippable[CtxOut, CtxOut2]): HandlerAspect[Env1, zippable.Out] =
HandlerAspect {
Expand Down Expand Up @@ -125,6 +126,20 @@ final case class HandlerAspect[-Env, +CtxOut](
}
}

def applyHandlerContextAsEnvRoutes[Err](
routes: Routes[CtxOut, Err],
): Routes[Env, Err] = {
routes.transform[Env] { handler =>
for {
tuple <- protocol.incomingHandler
(state, (request, ctxOut)) = tuple
either <- Handler.fromZIO(handler(request)).either.provideEnvironment(ZEnvironment(ctxOut))
response <- Handler.fromZIO(protocol.outgoingHandler((state, either.merge)))
response <- if (either.isLeft) Handler.fail(response) else Handler.succeed(response)
} yield response
}
}

def applyHandler[Env1 <: Env](handler: RequestHandler[Env1, Response]): RequestHandler[Env1, Response] =
if (self == HandlerAspect.identity) handler
else {
Expand All @@ -141,14 +156,14 @@ final case class HandlerAspect[-Env, +CtxOut](
* Returns new middleware that transforms the context of the middleware to the
* specified constant.
*/
def as[CtxOut2](ctxOut2: => CtxOut2): HandlerAspect[Env, CtxOut2] =
def as[CtxOut2: Tag](ctxOut2: => CtxOut2): HandlerAspect[Env, CtxOut2] =
map(_ => ctxOut2)

/**
* Returns new middleware that transforms the context of the middleware using
* the specified function.
*/
def map[CtxOut2](f: CtxOut => CtxOut2): HandlerAspect[Env, CtxOut2] =
def map[CtxOut2: Tag](f: CtxOut => CtxOut2): HandlerAspect[Env, CtxOut2] =
HandlerAspect(protocol.mapIncoming { case (request, ctx) => (request, f(ctx)) })

/**
Expand Down Expand Up @@ -351,7 +366,7 @@ private[http] trait HandlerAspects extends zio.http.internal.HeaderModifier[Hand
* requests to be passed on to the app, and provides a context to the request
* handlers.
*/
final def customAuthProviding[Context](
final def customAuthProviding[Context: Tag](
provide: Request => Option[Context],
responseHeaders: Headers = Headers.empty,
responseStatus: Status = Status.Unauthorized,
Expand All @@ -363,7 +378,7 @@ private[http] trait HandlerAspects extends zio.http.internal.HeaderModifier[Hand
* requests to be passed on to the app, and provides a context to the request
* handlers.
*/
def customAuthProvidingZIO[Env, Context](
def customAuthProvidingZIO[Env, Context: Tag](
provide: Request => ZIO[Env, Nothing, Option[Context]],
responseHeaders: Headers = Headers.empty,
responseStatus: Status = Status.Unauthorized,
Expand Down Expand Up @@ -454,7 +469,7 @@ private[http] trait HandlerAspects extends zio.http.internal.HeaderModifier[Hand
* another based on the result of the predicate, applied to the incoming
* request's headers.
*/
def ifHeaderThenElse[Env, Ctx](
def ifHeaderThenElse[Env, Ctx: Tag](
condition: Headers => Boolean,
)(
ifTrue: HandlerAspect[Env, Ctx],
Expand All @@ -467,7 +482,7 @@ private[http] trait HandlerAspects extends zio.http.internal.HeaderModifier[Hand
* another based on the result of the predicate, applied to the incoming
* request's method.
*/
def ifMethodThenElse[Env, Ctx](
def ifMethodThenElse[Env, Ctx: Tag](
condition: Method => Boolean,
)(
ifTrue: HandlerAspect[Env, Ctx],
Expand All @@ -480,7 +495,7 @@ private[http] trait HandlerAspects extends zio.http.internal.HeaderModifier[Hand
* another based on the result of the predicate, applied to the incoming
* request.
*/
def ifRequestThenElse[Env, CtxOut](
def ifRequestThenElse[Env, CtxOut: Tag](
predicate: Request => Boolean,
)(
ifTrue: HandlerAspect[Env, CtxOut],
Expand All @@ -497,7 +512,7 @@ private[http] trait HandlerAspects extends zio.http.internal.HeaderModifier[Hand
* another based on the result of the predicate, effectfully applied to the
* incoming request.
*/
def ifRequestThenElseZIO[Env, CtxOut](
def ifRequestThenElseZIO[Env, CtxOut: Tag](
predicate: Request => ZIO[Env, Response, Boolean],
)(
ifTrue: HandlerAspect[Env, CtxOut],
Expand Down Expand Up @@ -525,7 +540,7 @@ private[http] trait HandlerAspects extends zio.http.internal.HeaderModifier[Hand
* incoming and outgoing requests. If the incoming handler fails, then the
* outgoing handler will not be invoked.
*/
def interceptHandler[Env, CtxOut](
def interceptHandler[Env, CtxOut: Tag](
incoming0: Handler[Env, Response, Request, (Request, CtxOut)],
)(
outgoing0: Handler[Env, Nothing, Response, Response],
Expand All @@ -537,7 +552,7 @@ private[http] trait HandlerAspects extends zio.http.internal.HeaderModifier[Hand
* incoming and outgoing requests. If the incoming handler fails, then the
* outgoing handler will not be invoked.
*/
def interceptHandlerStateful[Env, State0, CtxOut](
def interceptHandlerStateful[Env, State0, CtxOut: Tag](
incoming0: Handler[
Env,
Response,
Expand All @@ -553,7 +568,7 @@ private[http] trait HandlerAspects extends zio.http.internal.HeaderModifier[Hand
* Creates middleware that will apply the specified handler to incoming
* requests.
*/
def interceptIncomingHandler[Env, CtxOut](
def interceptIncomingHandler[Env, CtxOut: Tag](
handler: Handler[Env, Response, Request, (Request, CtxOut)],
): HandlerAspect[Env, CtxOut] =
interceptHandler(handler)(Handler.identity)
Expand Down
3 changes: 3 additions & 0 deletions zio-http/src/main/scala/zio/http/Routes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ final class Routes[-Env, +Err] private (val routes: Chunk[zio.http.Route[Env, Er
def @@[Env1 <: Env](aspect: Middleware[Env1]): Routes[Env1, Err] =
aspect(self)

def @@[Env1](aspect: HandlerAspect[Env1, Env]): Routes[Env1, Err] =
aspect.applyHandlerContextAsEnvRoutes(self)

def asEnvType[Env2](implicit ev: Env2 <:< Env): Routes[Env2, Err] =
self.asInstanceOf[Routes[Env2, Err]]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package zio.http.endpoint

import zio.stacktracer.TracingImplicits.disableAutoTrace
import zio.{Chunk, Trace, ZIO}
import zio.{Chunk, Tag, Trace, ZIO}

import zio.http.Header.Accept.MediaTypeWithQFactor
import zio.http._
Expand Down Expand Up @@ -52,7 +52,7 @@ sealed trait EndpointMiddleware { self =>
def ??(doc: Doc): EndpointMiddleware.Typed[In, Err, Out] =
EndpointMiddleware.Spec(input, output, error, doc)

def implement[R, S](incoming: In => ZIO[R, Err, S])(
def implement[R, S: Tag](incoming: In => ZIO[R, Err, S])(
outgoing: S => ZIO[R, Err, Out],
)(implicit trace: Trace): HandlerAspect[R, S] =
HandlerAspect.interceptHandlerStateful(
Expand Down
4 changes: 2 additions & 2 deletions zio-http/src/test/scala/zio/http/endpoint/RoundtripSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ object RoundtripSpec extends ZIOHttpSpec {
val routes = endpointRoute

val app = routes.toHttpApp @@ alwaysFailingMiddleware
.implement(_ => ZIO.fail("FAIL"))(_ => ZIO.unit)
.implement(_ => ZIO.fail("FAIL"))((_: Any) => ZIO.unit)

for {
port <- Server.install(app)
Expand Down Expand Up @@ -361,7 +361,7 @@ object RoundtripSpec extends ZIOHttpSpec {
val routes = endpointRoute

val app = routes.toHttpApp @@ alwaysFailingMiddleware
.implement(_ => ZIO.fail("FAIL"))(_ => ZIO.unit)
.implement(_ => ZIO.fail("FAIL"))((_: Any) => ZIO.unit)

for {
port <- Server.install(app)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@

package zio.http.internal.middlewares

import java.net.URLDecoder

import zio.test.Assertion._
import zio.test._
import zio.{Ref, ZIO}

import zio.http.Header.ContentTransferEncoding.Base64
import zio.http._
import zio.http.internal.HttpAppTestExtensions

Expand Down Expand Up @@ -77,6 +80,20 @@ object AuthSpec extends ZIOHttpSpec with HttpAppTestExtensions {
} @@ basicAuthContextM).merge.mapZIO(_.body.asString)
assertZIO(app.runZIO(Request.get(URL.empty).copy(headers = successBasicHeader)))(equalTo("user"))
},
test("Extract basic auth username and pw via context providing through environment") {
val app = {
Routes(
(Method.GET / "context") -> Handler.fromZIO(
ZIO.serviceWith[Header.Authorization.Basic](s => Response.text(s"${s.username}:${s.password}")),
),
) @@ Middleware.customAuthProviding[Header.Authorization.Basic](_.headers.get(Header.Authorization).collect {
case basicAuth @ Header.Authorization.Basic(_, _) => basicAuth
})
}.toHttpApp
assertZIO(
app.runZIO(Request.get(URL.root / "context").copy(headers = successBasicHeader)).flatMap(_.body.asString),
)(equalTo("user:resu"))
},
test("Extract username via context with Routes") {
val app = {
Routes(
Expand Down

0 comments on commit 3fc353e

Please sign in to comment.