How to get a value of HandlerAspect's `CtxOut` type in request processing

104 views Asked by At

In zio-http, one has the option to write middleware that provide some sort of context around a request; as a concrete example, we might think of an auth middleware that looks up some package of session information before processing a request:

case class UserSession(sessionId: UUID, username: String)

val authMiddleware: HandlerAspect[Any, UserSession] = HandlerAspect.customAuthProvidingZIO {  request =>
  ZIO.succeed(request.headers.get("X-Session-ID").flatMap {
    case None => ZIO.succeed(None)
    case Some(sessionId) => fetchSession(sessionId).catchAll(_ => ZIO.succeed(None))
  }
}

We can attach this middleware to a Routes to ensure that any request coming in with no X-Session-ID header, or a session ID that our session cache doesn't know about, gets rejected. My question is, if the middleware succeeds in producing a UserSession, how can I get that value and use it in the request handler, something like

// this doesn't work
Routes(
  Method.GET / "foo" -> handler { (userSession: UserSession, request: Request) =>
    fooDatabase.getAll(userSession.username)
  }
) @@ authMiddleware

One reason this doesn't work, of course, is that the middleware isn't even attached to the Routes until after it's created, so there's something of a chicken-and-egg problem; but that seems to be kinda the point of middleware, and this GitHub issue and this PR have titles at least that suggest that they're trying to achieve something similar to this, though I don't see anything exactly like what I'm looking for in the PR code.

2

There are 2 answers

0
MSmedberg On BEST ANSWER

Instead of attaching the middleware/HandlerAspect to the "outside" of the Routes as in the snippet in the question, it turns out that it's possible to reference the middleware in the definition of each Route itself:

val authMiddleware: HandlerAspect[Any, UserSession] = ???
Routes(
  Method.GET / "foo" -> authMiddleware -> handler { (userSession: UserSession, request: Request) =>
    ...business logic goes here... }
)

(Kudos to user zzyzzyxx on the ZIO Discord for this find.)

0
Matthias Berndt On

The only way I've found to do that is to go through the ZIO environment. Assuming you have authMiddleware: HandlerAspect[Any, UserSession] and routes: Routes[UserSession, Err], you can turn the latter into a Routes[Any, Err] by calling the following on it:

      .transform: handler =>
         authMiddleware.applyHandlerContext:
            Handler.fromFunctionZIO: (userSession, req) =>
              handler(req).provideLayer(ZLayer.succeed(userSession))

But at this point I can't help but wonder why I should even bother with the whole HandlerAspect thing.