diff --git a/example/src/main/scala/example/PlainTextBenchmarkServer.scala b/example/src/main/scala/example/PlainTextBenchmarkServer.scala index 17952e0e5d..e68ca77ca7 100644 --- a/example/src/main/scala/example/PlainTextBenchmarkServer.scala +++ b/example/src/main/scala/example/PlainTextBenchmarkServer.scala @@ -1,7 +1,7 @@ package example import io.netty.util.AsciiString -import zhttp.http._ +import zhttp.http.{Http, _} import zhttp.service.server.ServerChannelFactory import zhttp.service.{EventLoopGroup, Server} import zio.{App, ExitCode, UIO, URIO} @@ -11,27 +11,43 @@ import zio.{App, ExitCode, UIO, URIO} */ object Main extends App { - private val message: String = "Hello, World!" + private val plainTextMessage: String = "Hello, World!" + private val jsonMessage: String = """{"greetings": "Hello World!"}""" + + private val plaintextPath = "/plaintext" + private val jsonPath = "/json" private val STATIC_SERVER_NAME = AsciiString.cached("zio-http") - private val frozenResponse = Response - .text(message) + private val frozenJsonResponse = Response + .json(jsonMessage) + .withServerTime + .withServer(STATIC_SERVER_NAME) + .freeze + + private val frozenPlainTextResponse = Response + .text(plainTextMessage) .withServerTime .withServer(STATIC_SERVER_NAME) .freeze - override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = { - frozenResponse + private def plainTextApp(response: Response) = Http.fromHExit(HExit.succeed(response)).whenPathEq(plaintextPath) + + private def jsonApp(json: Response) = Http.fromHExit(HExit.succeed(json)).whenPathEq(jsonPath) + + private def app = for { + plainTextResponse <- frozenPlainTextResponse + jsonResponse <- frozenJsonResponse + } yield plainTextApp(plainTextResponse) ++ jsonApp(jsonResponse) + + override def run(args: List[String]): URIO[zio.ZEnv, ExitCode] = + app .flatMap(server(_).make.useForever) .provideCustomLayer(ServerChannelFactory.auto ++ EventLoopGroup.auto(8)) .exitCode - } - private val path = "/plaintext" - private def app(response: Response) = Http.fromHExit(HExit.succeed(response)).whenPathEq(path) - private def server(response: Response) = - Server.app(app(response)) ++ + private def server(app: HttpApp[Any, Nothing]) = + Server.app(app) ++ Server.port(8080) ++ Server.error(_ => UIO.unit) ++ Server.disableLeakDetection ++ diff --git a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCombineEval.scala b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCombineEval.scala index eca25652d3..8894c6540a 100644 --- a/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCombineEval.scala +++ b/zio-http-benchmarks/src/main/scala/zhttp.benchmarks/HttpCombineEval.scala @@ -14,13 +14,13 @@ class HttpCombineEval { private val spec = (0 to MAX).foldLeft(app)((a, _) => a ++ app) @Benchmark - def benchmarkNotFound(): Unit = { + def empty(): Unit = { spec.execute(-1) () } @Benchmark - def benchmarkOk(): Unit = { + def ok(): Unit = { spec.execute(0) () } diff --git a/zio-http/src/main/scala/zhttp/http/Http.scala b/zio-http/src/main/scala/zhttp/http/Http.scala index 8332e143c2..1d08192177 100644 --- a/zio-http/src/main/scala/zhttp/http/Http.scala +++ b/zio-http/src/main/scala/zhttp/http/Http.scala @@ -223,7 +223,7 @@ sealed trait Http[-R, +E, -A, +B] extends (A => ZIO[R, Option[E], B]) { self => * Named alias for `++` */ final def defaultWith[R1 <: R, E1 >: E, A1 <: A, B1 >: B](other: Http[R1, E1, A1, B1]): Http[R1, E1, A1, B1] = - self.foldHttp(Http.fail, Http.die, Http.succeed, other) + Http.Combine(self, other) /** * Delays production of output B for the specified duration of time @@ -596,6 +596,16 @@ sealed trait Http[-R, +E, -A, +B] extends (A => ZIO[R, Option[E], B]) { self => } catch { case e: Throwable => HExit.die(e) } + + case Combine(self, other) => { + self.execute(a) match { + case HExit.Empty => other.execute(a) + case exit: HExit.Success[_] => exit.asInstanceOf[HExit[R, E, B]] + case exit: HExit.Failure[_] => exit.asInstanceOf[HExit[R, E, B]] + case exit: HExit.Die => exit + case exit @ HExit.Effect(_) => exit.defaultWith(other.execute(a)).asInstanceOf[HExit[R, E, B]] + } + } } } @@ -1053,6 +1063,11 @@ object Http { private case class Attempt[A](a: () => A) extends Http[Any, Nothing, Any, A] + private final case class Combine[R, E, EE, A, B, BB]( + self: Http[R, E, A, B], + other: Http[R, EE, A, BB], + ) extends Http[R, EE, A, BB] + private final case class FromHExit[R, E, B](h: HExit[R, E, B]) extends Http[R, E, Any, B] private final case class When[R, E, A, B](f: A => Boolean, other: Http[R, E, A, B]) extends Http[R, E, A, B] diff --git a/zio-http/src/test/scala/zhttp/http/HttpSpec.scala b/zio-http/src/test/scala/zhttp/http/HttpSpec.scala index 887b95a428..3330a5caa7 100644 --- a/zio-http/src/test/scala/zhttp/http/HttpSpec.scala +++ b/zio-http/src/test/scala/zhttp/http/HttpSpec.scala @@ -169,6 +169,33 @@ object HttpSpec extends DefaultRunnableSpec with HExitAssertion { val b = Http.collect[Int] { case 2 => "B" } val actual = (a ++ b).execute(3) assert(actual)(isEmpty) + } + + test("should not resolve") { + val a = Http.empty + val b = Http.empty + val c = Http.empty + val actual = (a ++ b ++ c).execute(()) + assert(actual)(isEmpty) + } + + test("should fail with second") { + val a = Http.empty + val b = Http.fail(100) + val c = Http.succeed("A") + val actual = (a ++ b ++ c).execute(()) + assert(actual)(isFailure(equalTo(100))) + } + + test("should resolve third") { + val a = Http.empty + val b = Http.empty + val c = Http.succeed("C") + val actual = (a ++ b ++ c).execute(()) + assert(actual)(isSuccess(equalTo("C"))) + } + + testM("should resolve second") { + val a = Http.fromHExit(HExit.Effect(ZIO.fail(None))) + val b = Http.succeed(2) + val actual = (a ++ b).execute(()).toZIO.either + assertM(actual)(isRight) }, ) + suite("asEffect")(