Skip to content

Commit

Permalink
Merge branch 'main' into rich-text-codec-char-in-opt
Browse files Browse the repository at this point in the history
  • Loading branch information
987Nabil committed Nov 21, 2023
2 parents cc451fc + dee5c58 commit 1623bc8
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 15 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ lazy val zioHttpBenchmarks = (project in file("zio-http-benchmarks"))
// "com.softwaremill.sttp.tapir" %% "tapir-akka-http-server" % "1.1.0",
"com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % "1.5.1",
"com.softwaremill.sttp.tapir" %% "tapir-json-circe" % "1.5.1",
"com.softwaremill.sttp.client3" %% "core" % "3.9.0",
"com.softwaremill.sttp.client3" %% "core" % "3.9.1",
// "dev.zio" %% "zio-interop-cats" % "3.3.0",
"org.slf4j" % "slf4j-api" % "2.0.9",
"org.slf4j" % "slf4j-simple" % "2.0.9",
Expand Down
4 changes: 2 additions & 2 deletions project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import sbt.Keys.scalaVersion

object Dependencies {
val JwtCoreVersion = "9.1.1"
val NettyVersion = "4.1.100.Final"
val NettyVersion = "4.1.101.Final"
val NettyIncubatorVersion = "0.0.20.Final"
val ScalaCompactCollectionVersion = "2.11.0"
val ZioVersion = "2.0.18"
val ZioVersion = "2.0.19"
val ZioCliVersion = "0.5.0"
val ZioSchemaVersion = "0.4.15"
val SttpVersion = "3.3.18"
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.9.6
sbt.version=1.9.7
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.0")
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.6")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.3")
addSbtPlugin("io.spray" % "sbt-revolver" % "0.10.0")
addSbtPlugin("com.github.sbt" % "sbt-github-actions" % "0.18.0")
addSbtPlugin("com.github.sbt" % "sbt-github-actions" % "0.19.0")
addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.12")
addSbtPlugin("dev.zio" % "zio-sbt-website" % "0.3.10")
addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.10.0")
Expand Down
13 changes: 13 additions & 0 deletions zio-http/src/main/scala/zio/http/Handler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,19 @@ sealed trait Handler[-R, +Err, -In, +Out] { self =>
): Handler[R1, Err1, In, Out1] =
self.foldHandler(err => Handler.fromZIO(f(err)), Handler.succeed(_))

/**
* Transforms all failures of the handler effectfully except pure
* interruption.
*/
final def mapErrorCauseZIO[R1 <: R, Err1, Out1 >: Out](
f: Cause[Err] => ZIO[R1, Err1, Out1],
)(implicit trace: Trace): Handler[R1, Err1, In, Out1] =
self.foldCauseHandler(
err =>
if (err.isInterruptedOnly) Handler.failCause(err.asInstanceOf[Cause[Nothing]]) else Handler.fromZIO(f(err)),
Handler.succeed(_),
)

/**
* Returns a new handler where the error channel has been merged into the
* success channel to their common combined type.
Expand Down
65 changes: 64 additions & 1 deletion zio-http/src/main/scala/zio/http/Middleware.scala
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,68 @@ object Middleware extends HandlerAspects {
}
}

def logAnnotate(key: => String, value: => String)(implicit trace: Trace): Middleware[Any] =
logAnnotate(LogAnnotation(key, value))

def logAnnotate(logAnnotation: => LogAnnotation, logAnnotations: LogAnnotation*)(implicit
trace: Trace,
): Middleware[Any] =
logAnnotate((logAnnotation +: logAnnotations).toSet)

def logAnnotate(logAnnotations: => Set[LogAnnotation])(implicit trace: Trace): Middleware[Any] =
new Middleware[Any] {
def apply[Env1 <: Any, Err](routes: Routes[Env1, Err]): Routes[Env1, Err] =
routes.transform[Env1] { h =>
handler((req: Request) => ZIO.logAnnotate(logAnnotations)(h(req)))
}
}

/**
* Creates a middleware that will annotate log messages that are logged while
* a request is handled with log annotations derived from the request.
*/
def logAnnotate(fromRequest: Request => Set[LogAnnotation])(implicit trace: Trace): Middleware[Any] =
new Middleware[Any] {
def apply[Env1 <: Any, Err](routes: Routes[Env1, Err]): Routes[Env1, Err] =
routes.transform[Env1] { h =>
handler((req: Request) => ZIO.logAnnotate(fromRequest(req))(h(req)))
}
}

/**
* Creates a middleware that will annotate log messages that are logged while
* a request is handled with the names and the values of the specified
* headers.
*/
def logAnnotateHeaders(headerName: String, headerNames: String*)(implicit trace: Trace): Middleware[Any] =
new Middleware[Any] {
def apply[Env1 <: Any, Err](routes: Routes[Env1, Err]): Routes[Env1, Err] = {
val headers = headerName +: headerNames
routes.transform[Env1] { h =>
handler((req: Request) => {
val annotations = Set.newBuilder[LogAnnotation]
annotations.sizeHint(headers.length)
var i = 0
while (i < headers.length) {
val name = headers(i)
annotations += LogAnnotation(name, req.headers.get(name).mkString)
i += 1
}
ZIO.logAnnotate(annotations.result())(h(req))
})
}
}
}

/**
* Creates middleware that will annotate log messages that are logged while a
* request is handled with the names and the values of the specified headers.
*/
def logAnnotateHeaders(header: Header.HeaderType, headers: Header.HeaderType*)(implicit
trace: Trace,
): Middleware[Any] =
logAnnotateHeaders(header.name, headers.map(_.name): _*)

def timeout(duration: Duration)(implicit trace: Trace): Middleware[Any] =
new Middleware[Any] {
def apply[Env1 <: Any, Err](routes: Routes[Env1, Err]): Routes[Env1, Err] =
Expand Down Expand Up @@ -284,7 +346,8 @@ object Middleware extends HandlerAspects {
}

override def apply[Env1 <: Any, Err](routes: Routes[Env1, Err]): Routes[Env1, Err] = {
val mountpoint = Method.GET / path.segments.map(PathCodec.literal).reduceLeft(_ / _)
val mountpoint =
Method.GET / path.segments.map(PathCodec.literal).reduceLeftOption(_ / _).getOrElse(PathCodec.empty)
val pattern = mountpoint / trailing
val other = Routes(
pattern -> Handler
Expand Down
26 changes: 26 additions & 0 deletions zio-http/src/main/scala/zio/http/Route.scala
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,32 @@ sealed trait Route[-Env, +Err] { self =>
Handled(rpm.routePattern, handler2, location)
}

final def handleErrorCauseZIO(
f: Cause[Err] => ZIO[Any, Nothing, Response],
)(implicit trace: Trace): Route[Env, Nothing] =
self match {
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, 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))
}
}
rpm.aspect.applyHandlerContext(paramHandler.mapErrorCauseZIO(f))
}

Handled(rpm.routePattern, handler2, location)
}

/**
* Determines if the route is defined for the specified request.
*/
Expand Down
14 changes: 7 additions & 7 deletions zio-http/src/main/scala/zio/http/RoutePattern.scala
Original file line number Diff line number Diff line change
Expand Up @@ -222,16 +222,16 @@ object RoutePattern {
*/
val any: RoutePattern[Path] = RoutePattern(Method.ANY, PathCodec.trailing)

def apply(method: Method, path: Path): RoutePattern[Unit] =
path.segments.foldLeft[RoutePattern[Unit]](fromMethod(method)) { (pathSpec, segment) =>
pathSpec./[Unit](PathCodec.Segment(SegmentCodec.literal(segment)))
}

/**
* Constructs a route pattern from a method and a path literal. To match
* against any method, use [[zio.http.Method.ANY]]. The specified string may
* contain path segments, which are separated by slashes.
*/
def apply(method: Method, value: String): RoutePattern[Unit] = {
val path = Path(value)

path.segments.foldLeft[RoutePattern[Unit]](fromMethod(method)) { (pathSpec, segment) =>
pathSpec./[Unit](PathCodec.Segment(SegmentCodec.literal(segment)))
}
}
def apply(method: Method, pathString: String): RoutePattern[Unit] =
apply(method, Path(pathString))
}
3 changes: 3 additions & 0 deletions zio-http/src/main/scala/zio/http/Routes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ final class Routes[-Env, +Err] private (val routes: Chunk[zio.http.Route[Env, Er
def handleErrorCause(f: Cause[Err] => Response)(implicit trace: Trace): Routes[Env, Nothing] =
new Routes(routes.map(_.handleErrorCause(f)))

def handleErrorCauseZIO(f: Cause[Err] => ZIO[Any, Nothing, Response])(implicit trace: Trace): Routes[Env, Nothing] =
new Routes(routes.map(_.handleErrorCauseZIO(f)))

/**
* Returns new routes that have each been provided the specified environment,
* thus eliminating their requirement for any specific environment.
Expand Down
72 changes: 72 additions & 0 deletions zio-http/src/test/scala/zio/http/LogAnnotationMiddlewareSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package zio.http

import zio._
import zio.test._

object LogAnnotationMiddlewareSpec extends ZIOSpecDefault {
override def spec: Spec[TestEnvironment with Scope, Any] =
suite("LogAnnotationMiddlewareSpec")(
test("add static log annotation") {
val response = Routes
.singleton(
handler(ZIO.logWarning("Oh!") *> ZIO.succeed(Response.text("Hey logging!"))),
)
.@@(Middleware.logAnnotate("label", "value"))
.toHttpApp
.runZIO(Request.get("/"))

for {
_ <- response
logs <- ZTestLogger.logOutput
log = logs.filter(_.message() == "Oh!").head
} yield assertTrue(log.annotations.get("label").contains("value"))

},
test("add request method and path as annotation") {
val response = Routes
.singleton(
handler(ZIO.logWarning("Oh!") *> ZIO.succeed(Response.text("Hey logging!"))),
)
.@@(
Middleware.logAnnotate(req =>
Set(LogAnnotation("method", req.method.name), LogAnnotation("path", req.path.encode)),
),
)
.toHttpApp
.runZIO(Request.get("/"))

for {
_ <- response
logs <- ZTestLogger.logOutput
log = logs.filter(_.message() == "Oh!").head
} yield assertTrue(
log.annotations.get("method").contains("GET"),
log.annotations.get("path").contains("/"),
)
},
test("add headers as annotation") {
val response = Routes
.singleton(
handler(ZIO.logWarning("Oh!") *> ZIO.succeed(Response.text("Hey logging!"))),
)
.@@(Middleware.logAnnotateHeaders("header"))
.@@(Middleware.logAnnotateHeaders(Header.UserAgent.name))
.toHttpApp
.runZIO {
Request
.get("/")
.addHeader("header", "value")
.addHeader(Header.UserAgent.Product("zio-http", Some("3.0.0")))
}

for {
_ <- response
logs <- ZTestLogger.logOutput
log = logs.filter(_.message() == "Oh!").head
} yield assertTrue(
log.annotations.get("header").contains("value"),
log.annotations.get(Header.UserAgent.name).contains("zio-http/3.0.0"),
)
},
)
}
18 changes: 16 additions & 2 deletions zio-http/src/test/scala/zio/http/RouteSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package zio.http

import scala.collection.Seq

import zio._
import zio.test._

Expand Down Expand Up @@ -64,5 +62,21 @@ object RouteSpec extends ZIOHttpSpec {
} yield assertTrue(cnt == 2)
},
),
suite("error handle")(
test("handleErrorCauseZIO should execute a ZIO effect") {
val route = Method.GET / "endpoint" -> handler { (req: Request) => ZIO.fail(new Exception("hmm...")) }
for {
p <- zio.Promise.make[Exception, String]

errorHandled = route
.handleErrorCauseZIO(c => p.failCause(c).as(Response.internalServerError))

request = Request.get(URL.decode("/endpoint").toOption.get)
response <- errorHandled.toHttpApp.runZIO(request)
result <- p.await.catchAllCause(c => ZIO.succeed(c.prettyPrint))

} yield assertTrue(extractStatus(response) == Status.InternalServerError, result.contains("hmm..."))
},
),
)
}

0 comments on commit 1623bc8

Please sign in to comment.