From 3401d7d289f478052c8d1ce7a99c6debd377a602 Mon Sep 17 00:00:00 2001 From: Nabil Abdel-Hafeez <7283535+987Nabil@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:28:53 +0200 Subject: [PATCH] Access latest route pattern when mapping route errors (#2823) (#2915) --- .../src/test/scala/zio/http/RoutesSpec.scala | 18 ++ .../src/main/scala/zio/http/Response.scala | 4 +- .../src/main/scala/zio/http/Route.scala | 175 ++++++++++-------- 3 files changed, 115 insertions(+), 82 deletions(-) diff --git a/zio-http/jvm/src/test/scala/zio/http/RoutesSpec.scala b/zio-http/jvm/src/test/scala/zio/http/RoutesSpec.scala index c11c5881e7..b61be9108f 100644 --- a/zio-http/jvm/src/test/scala/zio/http/RoutesSpec.scala +++ b/zio-http/jvm/src/test/scala/zio/http/RoutesSpec.scala @@ -18,6 +18,8 @@ package zio.http import zio.test._ +import zio.http.codec.PathCodec + object RoutesSpec extends ZIOHttpSpec { def extractStatus(response: Response): Status = response.status @@ -71,5 +73,21 @@ object RoutesSpec extends ZIOHttpSpec { ) } }, + test("nest routes") { + import PathCodec._ + import zio._ + case object IdFormatError + val routes = literal("to") / Routes( + Method.GET / "other" -> handler(ZIO.fail(IdFormatError)), + Method.GET / "do" / string("id") -> handler { (id: String, _: Request) => Response.text(s"GET /to/do/${id}") }, + ).handleError { case IdFormatError => + Response.badRequest + } + routes + .run( + path = Path.root / "to" / "do" / "123", + ) + .map(response => assertTrue(response.status == Status.Ok)) + }, ) } diff --git a/zio-http/shared/src/main/scala/zio/http/Response.scala b/zio-http/shared/src/main/scala/zio/http/Response.scala index 4dab7320dc..57df3fc2f3 100644 --- a/zio-http/shared/src/main/scala/zio/http/Response.scala +++ b/zio-http/shared/src/main/scala/zio/http/Response.scala @@ -159,8 +159,8 @@ object Response { case Left(failure: Throwable) => fromThrowable(failure) case Left(failure: Cause[_]) => fromCause(failure) case _ => - if (cause.isInterruptedOnly) error(Status.RequestTimeout, cause.prettyPrint.take(100)) - else error(Status.InternalServerError, cause.prettyPrint.take(100)) + if (cause.isInterruptedOnly) error(Status.RequestTimeout, cause.prettyPrint.take(10000)) + else error(Status.InternalServerError, cause.prettyPrint.take(10000)) } } diff --git a/zio-http/shared/src/main/scala/zio/http/Route.scala b/zio-http/shared/src/main/scala/zio/http/Route.scala index b47c28fc16..1a2c2eb5b3 100644 --- a/zio-http/shared/src/main/scala/zio/http/Route.scala +++ b/zio-http/shared/src/main/scala/zio/http/Route.scala @@ -71,23 +71,26 @@ sealed trait Route[-Env, +Err] { self => case Provided(route, env) => Provided(route.handleErrorCause(f), env) case Augmented(route, aspect) => Augmented(route.handleErrorCause(f), aspect) case Handled(routePattern, handler, location) => - Handled(routePattern, handler.mapErrorCause(c => f(c.asInstanceOf[Cause[Nothing]])), location) + Handled(routePattern, handler.map(_.mapErrorCause(c => f(c.asInstanceOf[Cause[Nothing]]))), location) case Unhandled(rpm, handler, zippable, location) => - val handler2: Handler[Env, Response, Request, Response] = { - val paramHandler = - Handler.fromFunctionZIO[(rpm.Context, Request)] { case (ctx, request) => - rpm.routePattern.decode(request.method, request.path) match { - case Left(error) => ZIO.dieMessage(error) - case Right(value) => - val params = rpm.zippable.zip(value, ctx) - - handler(zippable.zip(params, request)) + val handler2: Handler[Any, Nothing, RoutePattern[_], Handler[Env, Response, Request, Response]] = { + Handler.fromFunction[RoutePattern[_]] { pattern => + val paramHandler = { + Handler.fromFunctionZIO[(rpm.Context, Request)] { case (ctx, request) => + pattern.asInstanceOf[RoutePattern[rpm.PathInput]].decode(request.method, request.path) match { + case Left(error) => ZIO.dieMessage(error) + case Right(value) => + val params = rpm.zippable.zip(value, ctx) + + handler(zippable.zip(params, request)) + } } } - // Sandbox before applying aspect: - rpm.aspect.applyHandlerContext(paramHandler.mapErrorCause(f)) + // Sandbox before applying aspect: + rpm.aspect.applyHandlerContext(paramHandler.mapErrorCause(f)) + } } Handled(rpm.routePattern, handler2, location) @@ -106,21 +109,23 @@ sealed trait Route[-Env, +Err] { self => case Provided(route, env) => Provided(route.handleErrorCauseZIO(f), env) case Augmented(route, aspect) => Augmented(route.handleErrorCauseZIO(f), aspect) case Handled(routePattern, handler, location) => - Handled(routePattern, handler.mapErrorCauseZIO(c => f(c.asInstanceOf[Cause[Nothing]])), location) + Handled[Env](routePattern, handler.map(_.mapErrorCauseZIO(c => f(c.asInstanceOf[Cause[Nothing]]))), location) case Unhandled(rpm, handler, zippable, location) => - val handler2: Handler[Env, Response, Request, Response] = { - val paramHandler = - Handler.fromFunctionZIO[(rpm.Context, Request)] { case (ctx, request) => - rpm.routePattern.decode(request.method, request.path) match { - case Left(error) => ZIO.dieMessage(error) - case Right(value) => - val params = rpm.zippable.zip(value, ctx) - - handler(zippable.zip(params, request)) + val handler2: Handler[Any, Nothing, RoutePattern[_], Handler[Env, Response, Request, Response]] = { + Handler.fromFunction[RoutePattern[_]] { pattern => + val paramHandler = + Handler.fromFunctionZIO[(rpm.Context, Request)] { case (ctx, request) => + pattern.asInstanceOf[RoutePattern[rpm.PathInput]].decode(request.method, request.path) match { + case Left(error) => ZIO.dieMessage(error) + case Right(value) => + val params = rpm.zippable.zip(value, ctx) + + handler(zippable.zip(params, request)) + } } - } - rpm.aspect.applyHandlerContext(paramHandler.mapErrorCauseZIO(f)) + rpm.aspect.applyHandlerContext(paramHandler.mapErrorCauseZIO(f)) + } } Handled(rpm.routePattern, handler2, location) @@ -134,7 +139,7 @@ sealed trait Route[-Env, +Err] { self => self match { case Provided(route, env) => Provided(route.mapError(fxn), env) case Augmented(route, aspect) => Augmented(route.mapError(fxn), aspect) - case Handled(routePattern, handler, location) => Handled(routePattern, handler, location) + case Handled(routePattern, handler, location) => Handled[Env](routePattern, handler, location) case Unhandled(rpm, handler, zippable, location) => Unhandled(rpm, handler.mapError(fxn), zippable, location) } @@ -149,7 +154,7 @@ sealed trait Route[-Env, +Err] { self => self match { case Provided(route, env) => Provided(route.mapErrorZIO(fxn), env) case Augmented(route, aspect) => Augmented(route.mapErrorZIO(fxn), aspect) - case Handled(routePattern, handler, location) => Handled(routePattern, handler, location) + case Handled(routePattern, handler, location) => Handled[Env](routePattern, handler, location) case Unhandled(rpm, handler, zippable, location) => Unhandled(rpm, handler.mapErrorZIO(fxn), zippable, location) } @@ -173,33 +178,37 @@ sealed trait Route[-Env, +Err] { self => case Provided(route, env) => Provided(route.handleErrorRequestCause(f), env) case Augmented(route, aspect) => Augmented(route.handleErrorRequestCause(f), aspect) case Handled(routePattern, handler, location) => - Handled( + Handled[Env]( routePattern, - Handler.fromFunctionHandler[Request] { (req: Request) => - handler.mapErrorCause(c => f(req, c.asInstanceOf[Cause[Nothing]])) + handler.map { handler => + Handler.fromFunctionHandler[Request] { (req: Request) => + handler.mapErrorCause(c => f(req, c.asInstanceOf[Cause[Nothing]])) + } }, location, ) case Unhandled(rpm, handler, zippable, location) => - val handler2: Handler[Env, Response, Request, Response] = { - val paramHandler = - Handler.fromFunctionZIO[(rpm.Context, Request)] { case (ctx, request) => - rpm.routePattern.decode(request.method, request.path) match { - case Left(error) => ZIO.dieMessage(error) - case Right(value) => - val params = rpm.zippable.zip(value, ctx) - - handler(zippable.zip(params, request)) + val handler2: Handler[Any, Nothing, RoutePattern[_], Handler[Env, Response, Request, Response]] = { + Handler.fromFunction[RoutePattern[_]] { pattern => + val paramHandler = + Handler.fromFunctionZIO[(rpm.Context, Request)] { case (ctx, request) => + pattern.asInstanceOf[RoutePattern[rpm.PathInput]].decode(request.method, request.path) match { + case Left(error) => ZIO.dieMessage(error) + case Right(value) => + val params = rpm.zippable.zip(value, ctx) + + handler(zippable.zip(params, request)) + } } - } - // Sandbox before applying aspect: - rpm.aspect.applyHandlerContext( - Handler.fromFunctionHandler[(rpm.Context, Request)] { case (_, req) => - paramHandler.mapErrorCause(f(req, _)) - }, - ) + // Sandbox before applying aspect: + rpm.aspect.applyHandlerContext( + Handler.fromFunctionHandler[(rpm.Context, Request)] { case (_, req) => + paramHandler.mapErrorCause(f(req, _)) + }, + ) + } } Handled(rpm.routePattern, handler2, location) @@ -219,31 +228,35 @@ sealed trait Route[-Env, +Err] { self => case Provided(route, env) => Provided(route.handleErrorRequestCauseZIO(f), env) case Augmented(route, aspect) => Augmented(route.handleErrorRequestCauseZIO(f), aspect) case Handled(routePattern, handler, location) => - Handled( + Handled[Env]( routePattern, - Handler.fromFunctionHandler[Request] { (req: Request) => - handler.mapErrorCauseZIO(c => f(req, c.asInstanceOf[Cause[Nothing]])) + handler.map { handler => + Handler.fromFunctionHandler[Request] { (req: Request) => + handler.mapErrorCauseZIO(c => f(req, c.asInstanceOf[Cause[Nothing]])) + } }, location, ) case Unhandled(rpm, handler, zippable, location) => - val handler2: Handler[Env, Response, Request, Response] = { - val paramHandler = - Handler.fromFunctionZIO[(rpm.Context, Request)] { case (ctx, request) => - rpm.routePattern.decode(request.method, request.path) match { - case Left(error) => ZIO.dieMessage(error) - case Right(value) => - val params = rpm.zippable.zip(value, ctx) - - handler(zippable.zip(params, request)) + val handler2: Handler[Any, Nothing, RoutePattern[_], Handler[Env, Response, Request, Response]] = { + Handler.fromFunction[RoutePattern[_]] { pattern => + val paramHandler = + Handler.fromFunctionZIO[(rpm.Context, Request)] { case (ctx, request) => + pattern.asInstanceOf[RoutePattern[rpm.PathInput]].decode(request.method, request.path) match { + case Left(error) => ZIO.dieMessage(error) + case Right(value) => + val params = rpm.zippable.zip(value, ctx) + + handler(zippable.zip(params, request)) + } } - } - rpm.aspect.applyHandlerContext( - Handler.fromFunctionHandler[(rpm.Context, Request)] { case (_, req) => - paramHandler.mapErrorCauseZIO(f(req, _)) - }, - ) + rpm.aspect.applyHandlerContext( + Handler.fromFunctionHandler[(rpm.Context, Request)] { case (_, req) => + paramHandler.mapErrorCauseZIO(f(req, _)) + }, + ) + } } Handled(rpm.routePattern, handler2, location) @@ -264,7 +277,7 @@ sealed trait Route[-Env, +Err] { self => self match { case Provided(route, env) => Provided(route.nest(prefix), env) case Augmented(route, aspect) => Augmented(route.nest(prefix), aspect) - case Handled(routePattern, handler, location) => Handled(routePattern.nest(prefix), handler, location) + case Handled(routePattern, handler, location) => Handled[Env](routePattern.nest(prefix), handler, location) case Unhandled(rpm, handler, zippable, location) => Unhandled(rpm.prefix(prefix), handler, zippable, location) @@ -312,14 +325,14 @@ object Route { routePattern: RoutePattern[_], )(handler: Handler[Env, Response, Request, Response])(implicit trace: Trace): Route[Env, Nothing] = { // Sandbox before constructing: - Route.Handled(routePattern, handler.sandbox, Trace.empty) + Route.Handled(routePattern, Handler.fromFunction[RoutePattern[_]](_ => handler.sandbox), Trace.empty) } def handled[Params, Env](rpm: Route.Builder[Env, Params]): HandledConstructor[Env, Params] = new HandledConstructor[Env, Params](rpm) val notFound: Route[Any, Nothing] = - Handled(RoutePattern.any, Handler.notFound, Trace.empty) + Handled(RoutePattern.any, Handler.fromFunction[RoutePattern[_]](_ => Handler.notFound), Trace.empty) def route[Params](routePattern: RoutePattern[Params]): UnhandledConstructor[Any, Params] = route(Route.Builder(routePattern, HandlerAspect.identity)) @@ -331,20 +344,22 @@ object Route { def apply[Env1 <: Env, In]( handler: Handler[Env1, Response, In, Response], )(implicit zippable: Zippable.Out[Params, Request, In], trace: Trace): Route[Env1, Nothing] = { - val handler2: Handler[Env1, Response, Request, Response] = { - val paramHandler = - Handler.fromFunctionZIO[(rpm.Context, Request)] { case (ctx, request) => - rpm.routePattern.decode(request.method, request.path) match { - case Left(error) => ZIO.dieMessage(error) - case Right(value) => - val params = rpm.zippable.zip(value, ctx) - - handler(zippable.zip(params, request)) + val handler2: Handler[Any, Nothing, RoutePattern[_], Handler[Env1, Response, Request, Response]] = { + Handler.fromFunction[RoutePattern[_]] { _ => + val paramHandler = + Handler.fromFunctionZIO[(rpm.Context, Request)] { case (ctx, request) => + rpm.routePattern.decode(request.method, request.path) match { + case Left(error) => ZIO.dieMessage(error) + case Right(value) => + val params = rpm.zippable.zip(value, ctx) + + handler(zippable.zip(params, request)) + } } - } - // Sandbox before applying aspect: - rpm.aspect.applyHandlerContext(paramHandler.sandbox) + // Sandbox before applying aspect: + rpm.aspect.applyHandlerContext(paramHandler.sandbox) + } } Handled(rpm.routePattern, handler2, trace) @@ -442,11 +457,11 @@ object Route { private final case class Handled[-Env]( routePattern: RoutePattern[_], - handler: Handler[Env, Response, Request, Response], + handler: Handler[Any, Nothing, RoutePattern[_], Handler[Env, Response, Request, Response]], location: Trace, ) extends Route[Env, Nothing] { override def toHandler(implicit ev: Nothing <:< Response, trace: Trace): Handler[Env, Response, Request, Response] = - handler + Handler.fromZIO(handler(routePattern)).flatten override def toString() = s"Route.Handled(${routePattern}, ${location})" }