From 1fb411678c14c8ec0d06c6633a9787ed60474f25 Mon Sep 17 00:00:00 2001 From: Matthew Smedberg Date: Thu, 14 Mar 2024 18:41:33 -0600 Subject: [PATCH 1/4] Document HandlerAspect in middleware.md --- docs/dsl/middleware.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/dsl/middleware.md b/docs/dsl/middleware.md index efd974b2c0..2f7b1fa10a 100644 --- a/docs/dsl/middleware.md +++ b/docs/dsl/middleware.md @@ -285,3 +285,36 @@ object Example extends ZIOAppDefault { - [CORS](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/middleware_cors) - [CSRF](https://zio.github.io/zio-http/docs/v1.x/examples/advanced-examples/middleware_csrf) +## Handler Aspects + +Ordinary Middlewares are intended to bracket a request's execution by intercepting the request, possibly modifying it or short-circuiting its execution (for example if it fails authentication). +However, we sometimes want to gather some contextual information about a request and pass it alongside to the request's handler. +This can be achieved with the `HandlerAspect[Env, CtxOut]` type, which extends `Middleware[Env]`. +This middleware produces a value of type `CtxOut` on each request, which the routing DSL will accept just like a path component or query parameter: + +```scala +val sessionMiddleware: HandlerAspect[Env, Session] = createSessionMiddleware() +Routes( + Method.GET / "user" / int("userId") -> sessionMiddleware -> handler { (userId: Int, session: Session, request: Request) => + UserRepository.getUser(session.organizationId, userId) + } +) +``` + +In order to implement `createSessionMiddleware` in our example, we will need one or more `Handler`s: +```scala +def createSessionMiddleware[Env](): HandlerAspect[Env, Session] = { + val incomingHandler: Handler[Env, Response, Request, (Request, Session)] = + // session lookup logic here + + HandlerAspect.interceptIncomingHandler(incomingHandler) + + // or, if post-processing of the response is needed, + val outgoingHandler: Handler[Env, Nothing, Response, Response] = + // post-processing logic here + HandlerAspect.interceptHandler(incomingHandler)(outgoingHandler) +} +``` +Note the asymmetry of the type parameters of these two handlers: +In the incoming case, the `Err` parameter is `Response`; if the handler cannot produce a `Session`, then it is responsible for generating an error `Response` which will be returned to the client (possibly with modifications by other middlewares). +The outgoing handler, by contrast, has `Nothing` as its `Err` type, meaning that it **cannot** fail and must always produce a `Response`. From 77413b9f4371f2bd9e6a6fbfdd9434e34b78efac Mon Sep 17 00:00:00 2001 From: Matthew Smedberg Date: Thu, 14 Mar 2024 18:47:25 -0600 Subject: [PATCH 2/4] Clarify middleware intent and routing comments --- docs/dsl/middleware.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/dsl/middleware.md b/docs/dsl/middleware.md index 2f7b1fa10a..d2ec2c42fb 100644 --- a/docs/dsl/middleware.md +++ b/docs/dsl/middleware.md @@ -287,10 +287,11 @@ object Example extends ZIOAppDefault { ## Handler Aspects -Ordinary Middlewares are intended to bracket a request's execution by intercepting the request, possibly modifying it or short-circuiting its execution (for example if it fails authentication). +Ordinary Middlewares are intended to bracket a request's execution by intercepting the request, possibly modifying it or short-circuiting its execution +(for example if it fails authentication), and then performing some post-processing on the response. However, we sometimes want to gather some contextual information about a request and pass it alongside to the request's handler. This can be achieved with the `HandlerAspect[Env, CtxOut]` type, which extends `Middleware[Env]`. -This middleware produces a value of type `CtxOut` on each request, which the routing DSL will accept just like a path component or query parameter: +This middleware produces a value of type `CtxOut` on each request, which the routing DSL will accept just like a path component: ```scala val sessionMiddleware: HandlerAspect[Env, Session] = createSessionMiddleware() From 4c1898c05ce2ac9637e691946a57277b102bf76f Mon Sep 17 00:00:00 2001 From: Matthew Smedberg Date: Fri, 15 Mar 2024 08:02:46 -0600 Subject: [PATCH 3/4] Use mdoc snippet mode --- docs/dsl/middleware.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/dsl/middleware.md b/docs/dsl/middleware.md index d2ec2c42fb..cd8de00b87 100644 --- a/docs/dsl/middleware.md +++ b/docs/dsl/middleware.md @@ -293,7 +293,7 @@ However, we sometimes want to gather some contextual information about a request This can be achieved with the `HandlerAspect[Env, CtxOut]` type, which extends `Middleware[Env]`. This middleware produces a value of type `CtxOut` on each request, which the routing DSL will accept just like a path component: -```scala +```scala mdoc:silent:fail val sessionMiddleware: HandlerAspect[Env, Session] = createSessionMiddleware() Routes( Method.GET / "user" / int("userId") -> sessionMiddleware -> handler { (userId: Int, session: Session, request: Request) => @@ -303,7 +303,7 @@ Routes( ``` In order to implement `createSessionMiddleware` in our example, we will need one or more `Handler`s: -```scala +```scala mdoc:silent:fail def createSessionMiddleware[Env](): HandlerAspect[Env, Session] = { val incomingHandler: Handler[Env, Response, Request, (Request, Session)] = // session lookup logic here From beaf2503195ab5ac95fee4522f502949f67dd776 Mon Sep 17 00:00:00 2001 From: Matthew Smedberg Date: Mon, 25 Mar 2024 11:42:56 -0600 Subject: [PATCH 4/4] Make MDoc happy --- docs/dsl/middleware.md | 56 ++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/docs/dsl/middleware.md b/docs/dsl/middleware.md index cd8de00b87..36a7adf446 100644 --- a/docs/dsl/middleware.md +++ b/docs/dsl/middleware.md @@ -287,35 +287,43 @@ object Example extends ZIOAppDefault { ## Handler Aspects -Ordinary Middlewares are intended to bracket a request's execution by intercepting the request, possibly modifying it or short-circuiting its execution -(for example if it fails authentication), and then performing some post-processing on the response. +Ordinary Middlewares are intended to bracket a request's execution by intercepting the request, possibly modifying it or short-circuiting its execution, and then performing some post-processing on the response. However, we sometimes want to gather some contextual information about a request and pass it alongside to the request's handler. This can be achieved with the `HandlerAspect[Env, CtxOut]` type, which extends `Middleware[Env]`. -This middleware produces a value of type `CtxOut` on each request, which the routing DSL will accept just like a path component: +This middleware produces a value of type `CtxOut` on each request, which the routing DSL will accept just like a path component. +For example, to look up a `Session`, we might use a `sessionMiddleware` with type `HandlerAspect[Env, Session]`: -```scala mdoc:silent:fail -val sessionMiddleware: HandlerAspect[Env, Session] = createSessionMiddleware() +[//]: # (Invisible name declarations to get MDoc to compile) +```scala mdoc:invisible:reset +import zio.ZIO +import zio.http._ +type Env = Any + +case class Session(organizationId: Int) +val sessionMiddleware: HandlerAspect[Any, Session] = HandlerAspect.identity.map(_ => Session(0)) + +object UserRepository { + def getUser(organizationId: Int, userId: Int): ZIO[Any, Throwable, Response] = ??? +} +``` + +```scala mdoc:silent Routes( - Method.GET / "user" / int("userId") -> sessionMiddleware -> handler { (userId: Int, session: Session, request: Request) => - UserRepository.getUser(session.organizationId, userId) + Method.GET / "user" / int("userId") -> sessionMiddleware -> handler { + (userId: Int, session: Session, request: Request) => + UserRepository.getUser(session.organizationId, userId) } ) ``` - -In order to implement `createSessionMiddleware` in our example, we will need one or more `Handler`s: -```scala mdoc:silent:fail -def createSessionMiddleware[Env](): HandlerAspect[Env, Session] = { - val incomingHandler: Handler[Env, Response, Request, (Request, Session)] = - // session lookup logic here - - HandlerAspect.interceptIncomingHandler(incomingHandler) - - // or, if post-processing of the response is needed, - val outgoingHandler: Handler[Env, Nothing, Response, Response] = - // post-processing logic here - HandlerAspect.interceptHandler(incomingHandler)(outgoingHandler) -} +The `HandlerAspect` companion object provides a number of helpful constructors for these middlewares. +For this example, we would probably use `HandlerAspect.interceptHandler`, which wraps an incoming-request handler +as well as one which performs any necessary post-processing on the outgoing response: +```scala mdoc:compile-only +val incomingHandler: Handler[Env, Response, Request, (Request, Session)] = ??? +val outgoingHandler: Handler[Env, Nothing, Response, Response] = ??? +HandlerAspect.interceptHandler(incomingHandler)(outgoingHandler) ``` -Note the asymmetry of the type parameters of these two handlers: -In the incoming case, the `Err` parameter is `Response`; if the handler cannot produce a `Session`, then it is responsible for generating an error `Response` which will be returned to the client (possibly with modifications by other middlewares). -The outgoing handler, by contrast, has `Nothing` as its `Err` type, meaning that it **cannot** fail and must always produce a `Response`. + +Note the asymmetry in the type parameters of these two handlers: +in the incoming case, the handler emits a `Response` on the error-channel whenever the service cannot produce a `Session`, effectively short-circuiting the processing of this request. +The outgoing handler, by contrast, has `Nothing` as its `Err` type, meaning that it **cannot** fail and must always produce a `Response` on the success channel.