Skip to content

Commit bf5a01b

Browse files
authored
Sandbox routes later & don't pass handled responses into error handlers (#3146)
1 parent ad6dac9 commit bf5a01b

File tree

2 files changed

+71
-11
lines changed

2 files changed

+71
-11
lines changed

zio-http/jvm/src/test/scala/zio/http/RouteSpec.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ object RouteSpec extends ZIOHttpSpec {
135135
bodyString <- response.body.asString
136136
} yield assertTrue(extractStatus(response) == Status.InternalServerError, bodyString == "error")
137137
},
138+
test("handleErrorCause should pass through responses in error channel of handled routes") {
139+
val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.fail(Response.ok) }
140+
val errorHandled = route.handleErrorCause(_ => Response.text("error").status(Status.InternalServerError))
141+
val request = Request.get(URL.decode("/endpoint").toOption.get)
142+
errorHandled.toRoutes.runZIO(request).map(response => assertTrue(extractStatus(response) == Status.Ok))
143+
},
138144
test("handleErrorCauseZIO should handle defects") {
139145
val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.dieMessage("hmm...") }
140146
val errorHandled =
@@ -145,6 +151,18 @@ object RouteSpec extends ZIOHttpSpec {
145151
bodyString <- response.body.asString
146152
} yield assertTrue(extractStatus(response) == Status.InternalServerError, bodyString == "error")
147153
},
154+
test("handleErrorCauseZIO should pass through responses in error channel of handled routes") {
155+
val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.fail(Response.ok) }
156+
val request = Request.get(URL.decode("/endpoint").toOption.get)
157+
for {
158+
ref <- Ref.make(false)
159+
errorHandled = route.handleErrorCauseZIO(_ =>
160+
ref.set(true) *> ZIO.succeed(Response.text("error").status(Status.InternalServerError)),
161+
)
162+
response <- errorHandled.toRoutes.runZIO(request)
163+
refValue <- ref.get
164+
} yield assertTrue(extractStatus(response) == Status.Ok, !refValue)
165+
},
148166
test("handleErrorRequestCause should handle defects") {
149167
val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.dieMessage("hmm...") }
150168
val errorHandled =
@@ -155,6 +173,13 @@ object RouteSpec extends ZIOHttpSpec {
155173
bodyString <- response.body.asString
156174
} yield assertTrue(extractStatus(response) == Status.InternalServerError, bodyString == "error")
157175
},
176+
test("handleErrorRequestCause should pass through responses in error channel of handled routes") {
177+
val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.fail(Response.ok) }
178+
val errorHandled =
179+
route.handleErrorRequestCause((_, _) => Response.text("error").status(Status.InternalServerError))
180+
val request = Request.get(URL.decode("/endpoint").toOption.get)
181+
errorHandled.toRoutes.runZIO(request).map(response => assertTrue(extractStatus(response) == Status.Ok))
182+
},
158183
test("handleErrorRequestCauseZIO should handle defects") {
159184
val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.dieMessage("hmm...") }
160185
val errorHandled = route.handleErrorRequestCauseZIO((_, _) =>
@@ -166,6 +191,18 @@ object RouteSpec extends ZIOHttpSpec {
166191
bodyString <- response.body.asString
167192
} yield assertTrue(extractStatus(response) == Status.InternalServerError, bodyString == "error")
168193
},
194+
test("handleErrorRequestCauseZIO should pass through responses in error channel of handled routes") {
195+
val route = Method.GET / "endpoint" -> handler { (_: Request) => ZIO.fail(Response.ok) }
196+
val request = Request.get(URL.decode("/endpoint").toOption.get)
197+
for {
198+
ref <- Ref.make(false)
199+
errorHandled = route.handleErrorRequestCauseZIO((_, _) =>
200+
ref.set(true) *> ZIO.succeed(Response.text("error").status(Status.InternalServerError)),
201+
)
202+
response <- errorHandled.toRoutes.runZIO(request)
203+
refValue <- ref.get
204+
} yield assertTrue(extractStatus(response) == Status.Ok, !refValue)
205+
},
169206
test(
170207
"Routes with context can eliminate environment type partially when elimination produces intersection type environment",
171208
) {

zio-http/shared/src/main/scala/zio/http/Route.scala

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,21 @@ sealed trait Route[-Env, +Err] { self =>
4949
* Handles all typed errors in the route by converting them into responses.
5050
* This method can be used to convert a route that does not handle its errors
5151
* into one that does handle its errors.
52+
*
53+
* If the underlying handler uses the error channel to send responses, this
54+
* method will not pass the responses to the provided function.
5255
*/
5356
final def handleError(f: Err => Response)(implicit trace: Trace): Route[Env, Nothing] =
5457
self.handleErrorCauseZIO(c => ErrorResponseConfig.configRef.get.map(Response.fromCauseWith(c, _)(f)))
5558

59+
/**
60+
* Handles all typed errors in the route by converting them into a zio effect
61+
* that produces a response. This method can be used to convert a route that
62+
* does not handle its errors into one that does handle its errors.
63+
*
64+
* If the underlying handler uses the error channel to send responses, this
65+
* method will not pass the responses to the provided function.
66+
*/
5667
final def handleErrorZIO[Env1 <: Env](
5768
f: Err => ZIO[Env1, Nothing, Response],
5869
)(implicit trace: Trace): Route[Env1, Nothing] =
@@ -67,13 +78,16 @@ sealed trait Route[-Env, +Err] { self =>
6778
* Handles all typed errors, as well as all non-recoverable errors, by
6879
* converting them into responses. This method can be used to convert a route
6980
* that does not handle its errors into one that does handle its errors.
81+
*
82+
* If the underlying handler uses the error channel to send responses, this
83+
* method will not pass the responses to the provided function.
7084
*/
7185
final def handleErrorCause(f: Cause[Err] => Response)(implicit trace: Trace): Route[Env, Nothing] =
7286
self match {
7387
case Provided(route, env) => Provided(route.handleErrorCause(f), env)
7488
case Augmented(route, aspect) => Augmented(route.handleErrorCause(f), aspect)
7589
case Handled(routePattern, handler, location) =>
76-
Handled(routePattern, handler.map(_.mapErrorCause(c => f(c.asInstanceOf[Cause[Nothing]]))), location)
90+
Handled(routePattern, handler.map(_.mapErrorCause(_.failureOrCause.fold(identity, f))), location)
7791

7892
case Unhandled(pattern, handler0, zippable, location) =>
7993
val handler2: Handler[Any, Nothing, RoutePattern[_], Handler[Env, Response, Request, Response]] =
@@ -89,7 +103,6 @@ sealed trait Route[-Env, +Err] { self =>
89103
}
90104
}
91105

92-
// Sandbox before applying aspect:
93106
paramHandler.mapErrorCause(f)
94107
}
95108

@@ -101,6 +114,9 @@ sealed trait Route[-Env, +Err] { self =>
101114
* converting them into a ZIO effect that produces the response. This method
102115
* can be used to convert a route that does not handle its errors into one
103116
* that does handle its errors.
117+
*
118+
* If the underlying handler uses the error channel to send responses, this
119+
* method will not pass the responses to the provided function.
104120
*/
105121
final def handleErrorCauseZIO[Env1 <: Env](
106122
f: Cause[Err] => ZIO[Env1, Nothing, Response],
@@ -120,7 +136,7 @@ sealed trait Route[-Env, +Err] { self =>
120136
case Augmented(route, aspect) =>
121137
Augmented(route.handleErrorCauseZIO(f).asInstanceOf[Route[Any, Nothing]], aspect)
122138
case Handled(routePattern, handler, location) =>
123-
Handled(routePattern, handler.map(_.mapErrorCauseZIO(c => f(c.asInstanceOf[Cause[Nothing]]))), location)
139+
Handled(routePattern, handler.map(_.mapErrorCauseZIO(_.failureOrCause.fold(ZIO.succeed(_), f))), location)
124140

125141
case Unhandled(pattern, handler0, zippable, location) =>
126142
val handler2: Handler[Any, Nothing, RoutePattern[_], Handler[Env1, Response, Request, Response]] = {
@@ -174,6 +190,9 @@ sealed trait Route[-Env, +Err] { self =>
174190
* taking into account the request that caused the error. This method can be
175191
* used to convert a route that does not handle its errors into one that does
176192
* handle its errors.
193+
*
194+
* If the underlying handler uses the error channel to send responses, this
195+
* method will not pass the responses to the provided function.
177196
*/
178197
final def handleErrorRequest(f: (Err, Request) => Response)(implicit trace: Trace): Route[Env, Nothing] =
179198
self.handleErrorRequestCauseZIO((request, cause) =>
@@ -185,6 +204,9 @@ sealed trait Route[-Env, +Err] { self =>
185204
* converting them into responses, taking into account the request that caused
186205
* the error. This method can be used to convert a route that does not handle
187206
* its errors into one that does handle its errors.
207+
*
208+
* If the underlying handler uses the error channel to send responses, this
209+
* method will not pass the responses to the provided function.
188210
*/
189211
final def handleErrorRequestCause(f: (Request, Cause[Err]) => Response)(implicit trace: Trace): Route[Env, Nothing] =
190212
self match {
@@ -195,7 +217,7 @@ sealed trait Route[-Env, +Err] { self =>
195217
routePattern,
196218
handler0.map { h =>
197219
Handler.fromFunctionHandler { (req: Request) =>
198-
h.mapErrorCause(c => f(req, c.asInstanceOf[Cause[Nothing]]))
220+
h.mapErrorCause(_.failureOrCause.fold(identity, f(req, _)))
199221
}
200222
},
201223
location,
@@ -229,6 +251,9 @@ sealed trait Route[-Env, +Err] { self =>
229251
* account the request that caused the error. This method can be used to
230252
* convert a route that does not handle its errors into one that does handle
231253
* its errors.
254+
*
255+
* If the underlying handler uses the error channel to send responses, this
256+
* method will not pass the responses to the provided function.
232257
*/
233258
final def handleErrorRequestCauseZIO[Env1 <: Env](
234259
f: (Request, Cause[Err]) => ZIO[Env1, Nothing, Response],
@@ -252,7 +277,7 @@ sealed trait Route[-Env, +Err] { self =>
252277
routePattern,
253278
handler.map { handler =>
254279
Handler.fromFunctionHandler { (req: Request) =>
255-
handler.mapErrorCauseZIO(c => f(req, c.asInstanceOf[Cause[Nothing]]))
280+
handler.mapErrorCauseZIO(_.failureOrCause.fold(ZIO.succeed(_), f(req, _)))
256281
}
257282
},
258283
location,
@@ -336,10 +361,8 @@ object Route {
336361

337362
def handledIgnoreParams[Env](
338363
routePattern: RoutePattern[_],
339-
)(handler0: Handler[Env, Response, Request, Response])(implicit trace: Trace): Route[Env, Nothing] = {
340-
// Sandbox before constructing:
341-
Route.Handled(routePattern, handler((_: RoutePattern[_]) => handler0.sandbox), Trace.empty)
342-
}
364+
)(handler0: Handler[Env, Response, Request, Response])(implicit trace: Trace): Route[Env, Nothing] =
365+
Route.Handled(routePattern, handler((_: RoutePattern[_]) => handler0), Trace.empty)
343366

344367
def handled[Params, Env](rpm: RoutePattern[Params]): HandledConstructor[Env, Params] =
345368
new HandledConstructor[Env, Params](rpm)
@@ -365,7 +388,7 @@ object Route {
365388
}
366389

367390
// Sandbox before applying aspect:
368-
paramHandler.sandbox
391+
paramHandler
369392
}
370393
}
371394

@@ -414,7 +437,7 @@ object Route {
414437
location: Trace,
415438
) extends Route[Env, Nothing] {
416439
override def toHandler(implicit ev: Nothing <:< Response, trace: Trace): Handler[Env, Response, Request, Response] =
417-
Handler.fromZIO(handler(routePattern)).flatten
440+
Handler.fromZIO(handler(routePattern).map(_.sandbox)).flatten
418441

419442
override def toString = s"Route.Handled(${routePattern}, ${location})"
420443
}

0 commit comments

Comments
 (0)