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 2 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")
.settings(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._
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,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.js

import cats.effect.IO
import github4s.Github
import github4s.Github._
import github4s.cats.effect.js.Implicits._
import org.scalatest.{FlatSpec, Matchers}
import fr.hmil.roshttp.response.SimpleHttpResponse

class CatsEffectJSSpec 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 js integration" should "return a succeded result for a valid call" in {
val response = Github(accessToken).users
.get(validUsername)
.exec[IO, SimpleHttpResponse](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, SimpleHttpResponse](headerUserAgent)

val res = response.unsafeRunSync
res.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)
}
}
Expand Up @@ -44,37 +44,41 @@ trait HttpRequestBuilderExtensionJS {
new HttpRequestBuilderExtension[SimpleHttpResponse, Future] {

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

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

private[this] def runMap[A](
private[this] def runMapWrapper[A](
rb: HttpRequestBuilder[SimpleHttpResponse, Future],
mapResponse: SimpleHttpResponse => GHResponse[A]): Future[GHResponse[A]] = {

val params = rb.params.map {
case (key, value) => s"$key=$value"
} mkString "&"

val request = HttpRequest(rb.url)
.withMethod(Method(rb.httpVerb.verb))
.withQueryStringRaw(params)
.withHeader("content-type", "application/json")
.withHeaders(rb.authHeader.toList: _*)
.withHeaders(rb.headers.toList: _*)

rb.data
.map(d => request.send(CirceJSONBody(d)))
.getOrElse(request.send())
.map(toEntity[A](_, mapResponse))
.recoverWith {
case e =>
Future.successful(Either.left(UnexpectedException(e.getMessage)))
}
}
mapResponse: SimpleHttpResponse => GHResponse[A]): Future[GHResponse[A]] =
runMap[A, Future](rb, mapResponse)
}

protected def runMap[A, M[_]](
rb: HttpRequestBuilder[SimpleHttpResponse, M],
mapResponse: SimpleHttpResponse => GHResponse[A]): Future[GHResponse[A]] = {
val params = rb.params.map {
case (key, value) => s"$key=$value"
} mkString "&"

val request = HttpRequest(rb.url)
.withMethod(Method(rb.httpVerb.verb))
.withQueryStringRaw(params)
.withHeader("content-type", "application/json")
.withHeaders(rb.authHeader.toList: _*)
.withHeaders(rb.headers.toList: _*)

rb.data
.map(d => request.send(CirceJSONBody(d)))
.getOrElse(request.send())
.map(toEntity[A](_, mapResponse))
.recoverWith {
case e =>
Future.successful(Either.left(UnexpectedException(e.getMessage)))
}
}

def toEntity[A](
response: SimpleHttpResponse,
mapResponse: (SimpleHttpResponse) => GHResponse[A]): GHResponse[A] =
Expand Down
Expand Up @@ -67,7 +67,6 @@ trait HttpRequestBuilderExtensionJVM {
mapResponse)
)
case _ ⇒ C.capture(toEntity[A](request.asString, mapResponse))

}
}
}
Expand Down
Expand Up @@ -26,7 +26,7 @@ trait TestData extends DummyGithubUrls {
val headerUserAgent: Map[String, String] = Map("user-agent" -> "github4s")

val validUsername = "rafaparadela"
val invalidUsername = "GHInvalidaUserName"
val invalidUsername = "GHInvalidUserName"
val invalidPassword = "invalidPassword"

def validBasicAuth = s"Basic ${s"$validUsername:".getBytes.toBase64}"
Expand Down