Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tapErrorZIO and tapErrorCauseZIO to Route and Routes #2755

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 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 @@ -203,6 +203,58 @@ object RouteSpec extends ZIOHttpSpec {
refValue <- ref.get
} yield assertTrue(extractStatus(response) == Status.Ok, !refValue)
},
test("tapErrorZIO is not called when the route succeeds") {
val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.attempt(Response.ok) }
val errorTapped = route.tapErrorZIO(_ => ZIO.log("tapErrorZIO")).sandbox
for {
_ <- errorTapped(Request.get("/endpoint"))
didLog <- ZTestLogger.logOutput.map(out => out.find(_.message() == "tapErrorZIO").isDefined)
} yield assertTrue(!didLog)
},
test("tapErrorZIO is called when the route fails with an error") {
val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.fail(new Exception("hm...")) }
val errorTapped = route.tapErrorZIO(_ => ZIO.log("tapErrorZIO")).sandbox
for {
_ <- errorTapped(Request.get("/endpoint")).sandbox
didLog <- ZTestLogger.logOutput.map(out => out.find(_.message() == "tapErrorZIO").isDefined)
} yield assertTrue(didLog)
},
test("tapErrorZIO is not called when the route fails with a defect") {
val route: Route[Any, Unit] = Method.GET / "endpoint" -> handler { (_: Request) =>
ZIO.die(new Exception("hm..."))
}
val errorTapped = route.tapErrorZIO(_ => ZIO.log("tapErrorZIO")).sandbox
for {
_ <- errorTapped(Request.get("/endpoint")).sandbox
didLog <- ZTestLogger.logOutput.map(out => out.find(_.message() == "tapErrorZIO").isDefined)
} yield assertTrue(!didLog)
},
test("tapErrorCauseZIO is not called when the route succeeds") {
val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.attempt(Response.ok) }
val causeTapped = route.tapErrorCauseZIO(_ => ZIO.log("tapErrorCauseZIO")).sandbox
for {
_ <- causeTapped(Request.get("/endpoint"))
didLog <- ZTestLogger.logOutput.map(out => out.find(_.message() == "tapErrorCauseZIO").isDefined)
} yield assertTrue(!didLog)
},
test("tapErrorCauseZIO is called when the route fails with an error") {
val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.fail(new Exception("hm...")) }
val causeTapped = route.tapErrorCauseZIO(_ => ZIO.log("tapErrorCauseZIO")).sandbox
for {
_ <- causeTapped(Request.get("/endpoint")).sandbox
didLog <- ZTestLogger.logOutput.map(out => out.find(_.message() == "tapErrorCauseZIO").isDefined)
} yield assertTrue(didLog)
},
test("tapErrorCauseZIO is called when the route fails with a defect") {
val route: Route[Any, Unit] = Method.GET / "endpoint" -> handler { (_: Request) =>
ZIO.die(new Exception("hm..."))
}
val causeTapped = route.tapErrorCauseZIO(_ => ZIO.log("tapErrorCauseZIO")).sandbox
for {
_ <- causeTapped(Request.get("/endpoint")).sandbox
didLog <- ZTestLogger.logOutput.map(out => out.find(_.message() == "tapErrorCauseZIO").isDefined)
} yield assertTrue(didLog)
},
test(
"Routes with context can eliminate environment type partially when elimination produces intersection type environment",
) {
Expand Down
50 changes: 50 additions & 0 deletions zio-http/shared/src/main/scala/zio/http/Route.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package zio.http

import zio.Cause.Fail
import zio._

import zio.http.Route.CheckResponse
import zio.http.codec.PathCodec

/*
Expand Down Expand Up @@ -158,6 +160,42 @@ sealed trait Route[-Env, +Err] { self =>
Handled(pattern, handler2, location)
}

/**
* Effectfully peeks at the unhandled failure of this Route.
*/
final def tapErrorZIO[Err1 >: Err](
f: Err => ZIO[Any, Err1, Any],
)(implicit trace: Trace, ev: CheckResponse[Err]): Route[Env, Err1] =
self match {
case Provided(route, env) => Provided(route.tapErrorZIO(f), env)
case Augmented(route, aspect) => Augmented(route.tapErrorZIO(f), aspect)
case handled @ Handled(_, _, _) => handled
case Unhandled(rpm, handler, zippable, location) => Unhandled(rpm, handler.tapErrorZIO(f), zippable, location)
}

/**
* Effectfully peeks at the unhandled failure cause of this Route.
*/
final def tapErrorCauseZIO[Err1 >: Err](
f: Cause[Err] => ZIO[Any, Err1, Any],
)(implicit trace: Trace, ev: CheckResponse[Err]): Route[Env, Err1] =
self match {
case Provided(route, env) =>
Provided(route.tapErrorCauseZIO(f), env)
case Augmented(route, aspect) =>
Augmented(route.tapErrorCauseZIO(f), aspect)
case Handled(routePattern, handler, location) =>
Handled(
routePattern,
handler.map(_.tapErrorCauseZIO { cause0 =>
f(cause0.asInstanceOf[Cause[Nothing]]).catchAllCause(cause => ZIO.fail(Response.fromCause(cause)))
}),
location,
)
case Unhandled(rpm, handler, zippable, location) =>
Unhandled(rpm, handler.tapErrorCauseZIO(f), zippable, location)
}

/**
* Allows the transformation of the Err type through a function allowing one
* to build up a Routes in Stages targets the Unhandled case
Expand Down Expand Up @@ -464,4 +502,16 @@ object Route {
}
}

sealed trait CheckResponse[-A] { def isResponse: Boolean }
object CheckResponse {
implicit val response: CheckResponse[Response] = new CheckResponse[Response] {
val isResponse = true
}

// to avoid unnecessary allocation
private val otherInstance: CheckResponse[Nothing] = new CheckResponse[Nothing] {
val isResponse = false
}
implicit def other[A]: CheckResponse[A] = otherInstance.asInstanceOf[CheckResponse[A]]
}
}
12 changes: 12 additions & 0 deletions zio-http/shared/src/main/scala/zio/http/Routes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ final case class Routes[-Env, +Err](routes: Chunk[zio.http.Route[Env, Err]]) { s
def handleErrorCauseZIO(f: Cause[Err] => ZIO[Any, Nothing, Response])(implicit trace: Trace): Routes[Env, Nothing] =
new Routes(routes.map(_.handleErrorCauseZIO(f)))

/**
* Effectfully peeks at the unhandled failure of this Routes.
*/
def tapErrorZIO[Err1 >: Err](f: Err => ZIO[Any, Err1, Any])(implicit trace: Trace): Routes[Env, Err1] =
new Routes(routes.map(_.tapErrorZIO(f)))

/**
* Effectfully peeks at the unhandled failure cause of this Routes.
*/
def tapErrorCauseZIO[Err1 >: Err](f: Cause[Err] => ZIO[Any, Err1, Any])(implicit trace: Trace): Routes[Env, Err1] =
new Routes(routes.map(_.tapErrorCauseZIO(f)))

/**
* Allows the transformation of the Err type through an Effectful program
* allowing one to build up Routes in Stages delegates to the Route.
Expand Down
Loading