Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: Added combine operator #1106

Merged
merged 11 commits into from
Mar 10, 2022
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) => {
amitksingh1490 marked this conversation as resolved.
Show resolved Hide resolved
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