Skip to content

Commit

Permalink
Enhancement: Added combine operator (#1106)
Browse files Browse the repository at this point in the history
* enhancement: added combine operator

* added test cases with three apps

* refactor: plaintextBenchmarkServer

* test case updated

* removed flatten from test

* test case updated

* added test for collectHttp

* simplified tests

* added failing test

* fixed test

* refactor: cleaning up the execute method in Http

Co-authored-by: Tushar Mathur <tusharmath@gmail.com>
  • Loading branch information
ShrutiVerma97 and tusharmath authored Mar 10, 2022
1 parent 0de7b80 commit 006e318
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 14 deletions.
38 changes: 27 additions & 11 deletions example/src/main/scala/example/PlainTextBenchmarkServer.scala
Original file line number Diff line number Diff line change
@@ -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}
Expand All @@ -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 ++
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
()
}
Expand Down
17 changes: 16 additions & 1 deletion zio-http/src/main/scala/zhttp/http/Http.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]]
}
}
}
}

Expand Down Expand Up @@ -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]
Expand Down
27 changes: 27 additions & 0 deletions zio-http/src/test/scala/zhttp/http/HttpSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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")(
Expand Down

0 comments on commit 006e318

Please sign in to comment.