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

cats-effect module #155

Merged
merged 9 commits into from Jun 10, 2017
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 11 additions & 3 deletions build.sbt
Expand Up @@ -3,8 +3,8 @@ pgpPublicRing := file(s"$gpgFolder/pubring.gpg")
pgpSecretRing := file(s"$gpgFolder/secring.gpg")

lazy val root = (project in file("."))
.dependsOn(github4sJVM, github4sJS, scalaz, docs)
.aggregate(github4sJVM, github4sJS, scalaz, docs)
.dependsOn(github4sJVM, github4sJS, scalaz, catsEffectJVM, catsEffectJS, docs)
.aggregate(github4sJVM, github4sJS, scalaz, catsEffectJVM, catsEffectJS, docs)
.settings(noPublishSettings: _*)

lazy val github4s = (crossProject in file("github4s"))
Expand All @@ -23,7 +23,6 @@ lazy val github4s = (crossProject in file("github4s"))
.jsSettings(jsDeps: _*)
.jsSettings(sharedJsSettings: _*)
.jsSettings(testSettings: _*)

lazy val github4sJVM = github4s.jvm
lazy val github4sJS = github4s.js

Expand All @@ -39,3 +38,12 @@ lazy val scalaz = (project in file("scalaz"))
.settings(moduleName := "github4s-scalaz")
.settings(scalazDependencies: _*)
.dependsOn(github4sJVM)

lazy val catsEffect = (crossProject in file("cats-effect"))
.settings(moduleName := "github4s-cats-effect")
.crossDepSettings(catsEffectDependencies: _*)
.jsSettings(sharedJsSettings: _*)
.jsSettings(testSettings: _*)
.dependsOn(github4s)
lazy val catsEffectJVM = catsEffect.jvm
lazy val catsEffectJS = catsEffect.js
@@ -0,0 +1,47 @@
/*
* Copyright 2016-2017 47 Degrees, LLC. <http://www.47deg.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package github4s.cats.effect

import cats.effect.IO
import cats.Eval.later
import fr.hmil.roshttp.response.SimpleHttpResponse
import github4s.{HttpRequestBuilder, HttpRequestBuilderExtension, HttpRequestBuilderExtensionJS}
import github4s.GithubResponses.GHResponse
import io.circe.Decoder

import scala.concurrent.Future

trait IOHttpRequestBuilderExtensionJS extends HttpRequestBuilderExtensionJS {

import monix.execution.Scheduler.Implicits.global

implicit def extensionIOJS: HttpRequestBuilderExtension[SimpleHttpResponse, IO] =
new HttpRequestBuilderExtension[SimpleHttpResponse, IO] {

def run[A](rb: HttpRequestBuilder[SimpleHttpResponse, IO])(
implicit D: Decoder[A]): IO[GHResponse[A]] =
runMapWrapper[A](rb, decodeEntity[A])

def runEmpty(rb: HttpRequestBuilder[SimpleHttpResponse, IO]): IO[GHResponse[Unit]] =
runMapWrapper[Unit](rb, emptyResponse)

private[this] def runMapWrapper[A](
rb: HttpRequestBuilder[SimpleHttpResponse, IO],
mapResponse: SimpleHttpResponse => GHResponse[A]): IO[GHResponse[A]] =
IO.fromFuture(later(runMap[A, IO](rb, mapResponse)))
}
}
@@ -0,0 +1,19 @@
/*
* Copyright 2016-2017 47 Degrees, LLC. <http://www.47deg.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package github4s.cats.effect.js

object Implicits extends ImplicitsJS
@@ -0,0 +1,29 @@
/*
* Copyright 2016-2017 47 Degrees, LLC. <http://www.47deg.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package github4s.cats.effect.js

import cats.effect.IO
import fr.hmil.roshttp.response.SimpleHttpResponse
import github4s.HttpRequestBuilderExtensionJS
import github4s.cats.effect.{IOCaptureInstance, IOHttpRequestBuilderExtensionJS}
import github4s.free.interpreters.Interpreters
import github4s.implicits._

trait ImplicitsJS extends IOHttpRequestBuilderExtensionJS with IOCaptureInstance {
implicit val intInstanceIORosHttp =
new Interpreters[IO, SimpleHttpResponse]
}
@@ -0,0 +1,83 @@
/*
* Copyright 2016-2017 47 Degrees, LLC. <http://www.47deg.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package github4s.cats.effect.js

import cats.effect.IO
import github4s.Github
import github4s.Github._
import github4s.cats.effect.js.Implicits._
import org.scalatest.{Assertion, AsyncFunSuite, Matchers}
import fr.hmil.roshttp.response.SimpleHttpResponse
import github4s.GithubResponses.GHResponse
import github4s.free.domain.User
import org.scalactic.source.Position

import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future, Promise}
import scala.util.Try

class CatsEffectJSSpec extends AsyncFunSuite with Matchers {

implicit override def executionContext: ExecutionContextExecutor = ExecutionContext.global

def testEffectOnRunAsync[A](source: IO[GHResponse[A]], f: GHResponse[A] => Assertion)(
implicit pos: Position): Future[Assertion] = {

val effect = Promise[GHResponse[A]]()
val attempt = Promise[Try[GHResponse[A]]]()
effect.future.onComplete(attempt.success)

val io = source.runAsync {
case Right(s) => IO(effect.success(s))
case Left(e) => IO(effect.failure(e))
}

for (_ <- io.unsafeToFuture(); v <- attempt.future) yield {
v.toOption
.map { result =>
f(result)
}
.getOrElse(fail("effect attempt failed"))
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious as to how you found all this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cats-effects project, I only adapted it for our use case ;)


val accessToken = sys.env.get("GITHUB4S_ACCESS_TOKEN")
val headerUserAgent = Map("user-agent" -> "github4s")
val validUsername = "rafaparadela"
val invalidUsername = "GHInvalidUserName"
val okStatusCode = 200

test("return a succeded result for a valid call") {
val response = Github(accessToken).users
.get(validUsername)
.exec[IO, SimpleHttpResponse](headerUserAgent)

testEffectOnRunAsync(response, { r: GHResponse[User] =>
r.isRight shouldBe true
r.right.map(_.statusCode) shouldBe Right(okStatusCode)
})
}

test("return a failed result for an invalid call") {
val response = Github(accessToken).users
.get(invalidUsername)
.exec[IO, SimpleHttpResponse](headerUserAgent)

testEffectOnRunAsync(response, { r: GHResponse[User] =>
r.isLeft shouldBe true
})
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This spec is never run, I don't know why.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #156

@@ -0,0 +1,19 @@
/*
* Copyright 2016-2017 47 Degrees, LLC. <http://www.47deg.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package github4s.cats.effect.jvm

object Implicits extends ImplicitsJVM
@@ -0,0 +1,29 @@
/*
* Copyright 2016-2017 47 Degrees, LLC. <http://www.47deg.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package github4s.cats.effect.jvm

import cats.effect.IO
import github4s.HttpRequestBuilderExtensionJVM
import github4s.cats.effect.IOCaptureInstance
import github4s.free.interpreters.Interpreters
import github4s.implicits._
import scalaj.http.HttpResponse

trait ImplicitsJVM extends HttpRequestBuilderExtensionJVM with IOCaptureInstance {
implicit val intInstanceIOScalaJ =
new Interpreters[IO, HttpResponse[String]]
}
@@ -0,0 +1,51 @@
/*
* Copyright 2016-2017 47 Degrees, LLC. <http://www.47deg.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package github4s.cats.effect.jvm

import cats.effect.IO
import github4s.Github
import github4s.Github._
import github4s.cats.effect.jvm.Implicits._
import org.scalatest.{FlatSpec, Matchers}
import scalaj.http.HttpResponse

class CatsEffectJVMSpec extends FlatSpec with Matchers {
val accessToken = sys.env.get("GITHUB4S_ACCESS_TOKEN")
val headerUserAgent = Map("user-agent" -> "github4s")
val validUsername = "rafaparadela"
val invalidUsername = "GHInvalidUserName"
val okStatusCode = 200

"cats-effect jvm integration" should "return a succeded result for a valid call" in {
val response =
Github(accessToken).users.get(validUsername).exec[IO, HttpResponse[String]](headerUserAgent)

val res = response.unsafeRunSync
res.isRight shouldBe true
res.right.map(_.statusCode) shouldBe Right(okStatusCode)
}

it should "return a failed result for an invalid call" in {
val response =
Github(accessToken).users
.get(invalidUsername)
.exec[IO, HttpResponse[String]](headerUserAgent)

val res = response.unsafeRunSync
res.isLeft shouldBe true
}
}
@@ -0,0 +1,26 @@
/*
* Copyright 2016-2017 47 Degrees, LLC. <http://www.47deg.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package github4s.cats.effect

import cats.effect.IO
import github4s.free.interpreters.Capture

trait IOCaptureInstance {
implicit val ioCaptureInstance = new Capture[IO] {
override def capture[A](a: ⇒ A): IO[A] = IO.pure(a)
}
}
32 changes: 29 additions & 3 deletions docs/src/main/tut/docs.md
Expand Up @@ -56,8 +56,8 @@ an [HttpClient][http-client].

The previously mentioned implicit classes carry out of the box
instances for working with [scalaj][scalaj] (for JVM-compatible apps) and [roshttp][roshttp] (for
scala-js-compatible apps). Take into account that in the latter case, you can only use `Future` in
place of `M[_]`.
scala-js-compatible apps). Take into account that in the latter case, you can only use `Future` and
`cats.effect.IO` (if you pull in the `github4s-cats-effect` dependency) in place of `M[_]`.

A few examples follow with different `MonadError[M, Throwable]`.

Expand Down Expand Up @@ -115,7 +115,6 @@ object ProgramFuture {
```tut:silent
import scalaz.concurrent.Task
import github4s.scalaz.implicits._
import scalaj.http.HttpResponse

object ProgramTask {
val u4 = Github(accessToken).users.get("franciscodr").exec[Task, HttpResponse[String]]()
Expand All @@ -125,6 +124,33 @@ object ProgramTask {

Note that you'll need a dependency to the `github4s-scalaz` pacakge to leverage `scalaz.Task`.

### Using `cats.effect.IO`

On the JVM:
```scala
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't seem to get this example to compile with tut despite adding cats-effect to the doc deps, any ideas?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #158

import cats.effect.IO
import github4s.cats.effect.jvm.Implicits._

object ProgramTask {
val u5 = Github(accesstoken).users.get("juanpedromoreno").exec[IO, HttpResponse[String]]()
u5.unsafeRunSync
}
```

Using scala-js:
```scala
import github4s.cats.effect.js.Implicits._
import fr.hmil.roshttp.response.SimpleHttpResponse

object ProgramTask {
val u6 = Github(accesstoken).users.get("fedefernandez").exec[IO, SimpleHttpResponse]()
u6.unsafeRunAsync
}
```

Note that you'll need a dependency to the `github4s-cats-effect` package to leverage
`cats.effect.IO`.

## Specifying custom headers

The different `exec` methods let users include custom headers for any Github API request:
Expand Down