diff options
author | Adam Warski <adam@warski.org> | 2017-08-04 11:58:29 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-04 11:58:29 +0200 |
commit | ab566fd5cf0f54e8e69b2917b32e57f2321d49ee (patch) | |
tree | daf39b33bf972d5b538f85eb202367920121cf4a | |
parent | dbec6b509e89f21628dc90fd8a2ed8d299135b24 (diff) | |
parent | 5ce4a596d49e7da42b8a60d438094f3ba67ab3f5 (diff) | |
download | sttp-ab566fd5cf0f54e8e69b2917b32e57f2321d49ee.tar.gz sttp-ab566fd5cf0f54e8e69b2917b32e57f2321d49ee.tar.bz2 sttp-ab566fd5cf0f54e8e69b2917b32e57f2321d49ee.zip |
Merge pull request #21 from aeons/feature/cats-effect
Add support for cats effect with AHC backend
5 files changed, 94 insertions, 2 deletions
@@ -188,6 +188,7 @@ uri"$scheme://$subdomains.example.com?x=$vx&$params#$jumpTo" | `FutureAsyncHttpClientHandler` | `scala.concurrent.Future` | - | | `ScalazAsyncHttpClientHandler` | `scalaz.concurrent.Task` | - | | `MonixAsyncHttpClientHandler` | `monix.eval.Task` | `monix.reactive.Observable[ByteBuffer]` | +| `CatsAsyncHttpClientHandler` | `F[_]: cats.effect.Async` | - | | `OkHttpSyncClientHandler` | None (`Id`) | - | | `OkHttpFutureClientHandler` | `scala.concurrent.Future` | - | @@ -271,6 +272,8 @@ To use, add the following dependency to your project: "com.softwaremill.sttp" %% "async-http-client-handler-scalaz" % "0.0.4" // or "com.softwaremill.sttp" %% "async-http-client-handler-monix" % "0.0.4" +// or +"com.softwaremill.sttp" %% "async-http-client-handler-cats" % "0.0.4" ``` This handler depends on [async-http-client](https://github.com/AsyncHttpClient/async-http-client). @@ -284,6 +287,8 @@ The responses are wrapped depending on the dependency chosen in either a: dependency on `scalaz-concurrent`. * [Monix](https://monix.io) `Task`. There's a transitive dependency on `monix-eval`. +* Any type implementing the [Cats Effect](https://github.com/typelevel/cats-effect) `Async` +typeclass, such as `cats.effect.IO`. There's a transitive dependency on `cats-effect`. Next you'll need to add an implicit value: @@ -296,6 +301,9 @@ implicit val sttpHandler = ScalazAsyncHttpClientHandler() // or, if you're using the monix version: implicit val sttpHandler = MonixAsyncHttpClientHandler() +// or, if you're using the cats effect version: +implicit val sttpHandler = CatsAsyncHttpClientHandler[cats.effect.IO]() + // or, if you'd like to use custom configuration: implicit val sttpHandler = FutureAsyncHttpClientHandler.usingConfig(asyncHttpClientConfig) @@ -433,4 +441,5 @@ and pick a task you'd like to work on! * [Tomasz Szymański](https://github.com/szimano) * [Adam Warski](https://github.com/adamw) -* [Omar Alejandro Mainegra Sarduy](https://github.com/omainegra)
\ No newline at end of file +* [Omar Alejandro Mainegra Sarduy](https://github.com/omainegra) +* [Bjørn Madsen](https://github.com/aeons) diff --git a/async-http-client-handler/cats/src/main/scala/com/softwaremill/sttp/asynchttpclient/cats/CatsAsyncHttpClientHandler.scala b/async-http-client-handler/cats/src/main/scala/com/softwaremill/sttp/asynchttpclient/cats/CatsAsyncHttpClientHandler.scala new file mode 100644 index 0000000..fab3b8b --- /dev/null +++ b/async-http-client-handler/cats/src/main/scala/com/softwaremill/sttp/asynchttpclient/cats/CatsAsyncHttpClientHandler.scala @@ -0,0 +1,64 @@ +package com.softwaremill.sttp.asynchttpclient.cats + +import java.nio.ByteBuffer + +import cats.effect._ +import com.softwaremill.sttp.asynchttpclient.AsyncHttpClientHandler +import com.softwaremill.sttp.{MonadAsyncError, SttpHandler} +import org.asynchttpclient.{ + AsyncHttpClient, + AsyncHttpClientConfig, + DefaultAsyncHttpClient +} +import org.reactivestreams.Publisher + +import scala.language.higherKinds + +class CatsAsyncHttpClientHandler[F[_]: Async] private ( + asyncHttpClient: AsyncHttpClient, + closeClient: Boolean +) extends AsyncHttpClientHandler[F, Nothing]( + asyncHttpClient, + new AsyncMonad, + closeClient + ) { + override protected def streamBodyToPublisher( + s: Nothing): Publisher[ByteBuffer] = s // nothing is everything + + override protected def publisherToStreamBody( + p: Publisher[ByteBuffer]): Nothing = + throw new IllegalStateException("This handler does not support streaming") +} + +object CatsAsyncHttpClientHandler { + + def apply[F[_]: Async](): SttpHandler[F, Nothing] = + new CatsAsyncHttpClientHandler(new DefaultAsyncHttpClient(), + closeClient = true) + + def usingConfig[F[_]: Async]( + cfg: AsyncHttpClientConfig): SttpHandler[F, Nothing] = + new CatsAsyncHttpClientHandler(new DefaultAsyncHttpClient(cfg), + closeClient = true) + + def usingClient[F[_]: Async]( + client: AsyncHttpClient): SttpHandler[F, Nothing] = + new CatsAsyncHttpClientHandler(client, closeClient = false) +} + +private[cats] class AsyncMonad[F[_]](implicit F: Async[F]) + extends MonadAsyncError[F] { + + override def async[T]( + register: ((Either[Throwable, T]) => Unit) => Unit): F[T] = + F.async(register) + + override def unit[T](t: T): F[T] = F.pure(t) + + override def map[T, T2](fa: F[T], f: (T) => T2): F[T2] = F.map(fa)(f) + + override def flatMap[T, T2](fa: F[T], f: (T) => F[T2]): F[T2] = + F.flatMap(fa)(f) + + override def error[T](t: Throwable): F[T] = F.raiseError(t) +} @@ -45,6 +45,7 @@ lazy val rootProject = (project in file(".")) futureAsyncHttpClientHandler, scalazAsyncHttpClientHandler, monixAsyncHttpClientHandler, + catsAsyncHttpClientHandler, okhttpClientHandler, tests ) @@ -105,6 +106,16 @@ lazy val monixAsyncHttpClientHandler: Project = (project in file( ) ) dependsOn asyncHttpClientHandler +lazy val catsAsyncHttpClientHandler: Project = (project in file( + "async-http-client-handler/cats")) + .settings(commonSettings: _*) + .settings( + name := "async-http-client-handler-cats", + libraryDependencies ++= Seq( + "org.typelevel" %% "cats-effect" % "0.4" + ) + ) dependsOn asyncHttpClientHandler + lazy val okhttpClientHandler: Project = (project in file( "okhttp-client-handler")) .settings(commonSettings: _*) @@ -129,4 +140,4 @@ lazy val tests: Project = (project in file("tests")) ).map(_ % "test"), libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value % "test" ) dependsOn (core, akkaHttpHandler, futureAsyncHttpClientHandler, scalazAsyncHttpClientHandler, -monixAsyncHttpClientHandler, okhttpClientHandler) +monixAsyncHttpClientHandler, catsAsyncHttpClientHandler, okhttpClientHandler) diff --git a/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala b/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala index 5f17a7a..36c82ef 100644 --- a/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala +++ b/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala @@ -16,6 +16,7 @@ import com.typesafe.scalalogging.StrictLogging import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures} import org.scalatest.{BeforeAndAfterAll, FlatSpec, Matchers} import better.files._ +import com.softwaremill.sttp.asynchttpclient.cats.CatsAsyncHttpClientHandler import com.softwaremill.sttp.asynchttpclient.future.FutureAsyncHttpClientHandler import com.softwaremill.sttp.asynchttpclient.monix.MonixAsyncHttpClientHandler import com.softwaremill.sttp.asynchttpclient.scalaz.ScalazAsyncHttpClientHandler @@ -130,6 +131,9 @@ class BasicTests ForceWrappedValue.scalazTask) runTests("Async Http Client - Monix")(MonixAsyncHttpClientHandler(), ForceWrappedValue.monixTask) + runTests("Async Http Client - Cats Effect")( + CatsAsyncHttpClientHandler[cats.effect.IO](), + ForceWrappedValue.catsIo) runTests("OkHttpSyncClientHandler")(OkHttpSyncClientHandler(), ForceWrappedValue.id) runTests("OkHttpSyncClientHandler - Future")(OkHttpFutureClientHandler(), diff --git a/tests/src/test/scala/com/softwaremill/sttp/testHelpers.scala b/tests/src/test/scala/com/softwaremill/sttp/testHelpers.scala index 9bf68bb..558e9dd 100644 --- a/tests/src/test/scala/com/softwaremill/sttp/testHelpers.scala +++ b/tests/src/test/scala/com/softwaremill/sttp/testHelpers.scala @@ -53,6 +53,10 @@ trait ForceWrapped extends ScalaFutures { this: Suite => override def force[T](wrapped: monix.eval.Task[T]): T = wrapped.runAsync.futureValue } + val catsIo = new ForceWrappedValue[cats.effect.IO] { + override def force[T](wrapped: cats.effect.IO[T]): T = + wrapped.unsafeRunSync + } } implicit class ForceDecorator[R[_], T](wrapped: R[T]) { def force()(implicit fwv: ForceWrappedValue[R]): T = fwv.force(wrapped) |