Skip to content

Commit

Permalink
Merge pull request #18 from 47deg/rafa-replace-Id-by-Eval
Browse files Browse the repository at this point in the history
Provide interpreters with several monad instances
  • Loading branch information
Rafa Paradela committed Jun 6, 2016
2 parents 63674ae + 6b56f6c commit 6cc84e9
Show file tree
Hide file tree
Showing 22 changed files with 260 additions and 145 deletions.
21 changes: 17 additions & 4 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@ lazy val buildSettings = Seq(

lazy val dependencies = libraryDependencies ++= Seq(
"org.typelevel" %% "cats" % "0.4.0",
"org.scalaz" %% "scalaz-concurrent" % "7.1.4",
"org.scalaj" %% "scalaj-http" % "2.2.1",
"io.circe" %% "circe-core" % "0.3.0",
"io.circe" %% "circe-generic" % "0.3.0",
"io.circe" %% "circe-parser" % "0.3.0",
"com.typesafe" % "config" % "1.3.0",
"org.scalatest" %% "scalatest" % "2.2.6" % "test",
"com.ironcorelabs" %% "cats-scalatest" % "1.1.2" % "test",
"org.mock-server" % "mockserver-netty" % "3.10.4" % "test"
Expand Down Expand Up @@ -65,7 +63,12 @@ lazy val github4sSettings = buildSettings ++ dependencies ++ scalariformSettings

lazy val ghpagesSettings = ghpages.settings ++ Seq(git.remoteRepo := "git@github.com:47deg/github4s.git")

lazy val docsSettings = buildSettings ++ noPublishSettings ++ tutSettings ++ tutDirectoriesSettings ++ ghpagesSettings
lazy val docsSettings = buildSettings ++ docsDependencies ++ noPublishSettings ++ tutSettings ++ tutDirectoriesSettings ++ ghpagesSettings

lazy val docsDependencies = libraryDependencies ++= Seq(
"com.ironcorelabs" %% "cats-scalatest" % "1.1.2" % "test",
"org.mock-server" % "mockserver-netty" % "3.10.4" % "test"
)

lazy val github4s = (project in file("."))
.settings(moduleName := "github4s")
Expand All @@ -75,5 +78,15 @@ lazy val docs = (project in file("docs"))
.settings(moduleName := "github4s-docs")
.settings(docsSettings: _*)
.enablePlugins(JekyllPlugin)
.dependsOn(github4s)
.dependsOn(scalaz)

lazy val scalazDependencies = libraryDependencies ++= Seq(
"org.scalaz" %% "scalaz-concurrent" % "7.1.4"
)

lazy val scalazSettings = buildSettings ++ scalazDependencies ++ scalariformSettings

lazy val scalaz = (project in file("scalaz"))
.settings(moduleName := "github4s-scalaz")
.settings(scalazSettings: _*)
.dependsOn(github4s)
1 change: 1 addition & 0 deletions docs/src/jekyll/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ description: "A GitHub API wrapper written in Scala"
github_owner: 47deg
baseurl: /github4s
style: github4s
highlight_theme: tomorrow
docs: true

markdown: redcarpet
Expand Down
78 changes: 75 additions & 3 deletions docs/src/tut/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,78 @@
layout: docs
---

```tut
2 + 2
```
# Get started

WIP: Import

```tut:silent
import github4s.Github
```

WIP: Every Github4s api returns a `Free[GHResponse[A], A]` where `GHResonse[A]` is a type alias for `GHException Xor GHResult[A]`. GHResult contains the result `[A]` given by Github, but also the status code of the response and headers:

```scala
case class GHResult[A](result: A, statusCode: Int, headers: Map[String, IndexedSeq[String]])
```

For geting an user

```tut:silent
val user1 = Github().users.get("rafaparadela")
```

user1 in this case `Free[GHException Xor GHResult[User], User]` and we can run (`foldMap`) with `exec[M[_]]` where `M[_]` represent any type container that implements `MonadError[M, Throwable]`, for instance `cats.Eval`.

```tut:silent
import cats.Eval
import github4s.Github._
import github4s.implicits._
val u1 = user1.exec[Eval].value
```

WIP: As mentioned above `u1` should have an `GHResult[User]` in the right.

```tut:invisible
import cats.data.Xor
import github4s.GithubResponses.GHResult
```

```tut:book
u1 match {
case Xor.Right(GHResult(result, status, headers)) => result.login
case Xor.Left(e) => e.getMessage
}
```

WIP: With `Id`

```tut:silent
import cats.Id
val u2 = Github().users.get("raulraja").exec[Id]
```

WIP: With `Future`

```tut:silent
import cats.std.future._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.Await
val u3 = Github().users.get("dialelo").exec[Future]
Await.result(u3, 2.seconds)
```

WIP: With `scalaz.Task`

```tut:silent
import scalaz.concurrent.Task
import github4s.scalaz.implicits._
val u4 = Github().users.get("franciscodr").exec[Task]
u4.attemptRun
```

19 changes: 17 additions & 2 deletions docs/src/tut/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,22 @@ technologies:
- database: ["Database", "Lorem ipsum dolor sit amet, conse ctetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolo…"]
---

```tut
1 + 1
```tut:invisible
import org.scalatest._
import Matchers._
import cats.scalatest.XorMatchers.right
import cats.scalatest.XorValues
import cats.scalatest.XorValues._
import cats.Eval
import github4s.Github
import github4s.Github._
import github4s.implicits._
```

```tut:book
val user1 = Github().users.get("rafaparadela").exec[Eval].value
user1 shouldBe right
user1.value.result.login shouldBe "rafaparadela"
```

21 changes: 21 additions & 0 deletions scalaz/src/main/scala/github4s/scalaz/Implicits.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package github4s.scalaz

import cats.MonadError
import scalaz.concurrent.Task

object implicits {

implicit val g4sTaskMonadError: MonadError[Task, Throwable] = new MonadError[Task, Throwable] {

override def pure[A](x: A): Task[A] = Task.now(x)

override def map[A, B](fa: Task[A])(f: A B): Task[B] = fa.map(f)

override def flatMap[A, B](fa: Task[A])(ff: A Task[B]): Task[B] = fa.flatMap(ff)

override def raiseError[A](e: Throwable): Task[A] = Task.fail(e)

override def handleErrorWith[A](fa: Task[A])(f: Throwable Task[A]): Task[A] = fa.handleWith({ case x f(x) })
}

}
15 changes: 0 additions & 15 deletions src/main/scala/github4s/GithubApiConfig.scala

This file was deleted.

16 changes: 16 additions & 0 deletions src/main/scala/github4s/GithubDefaultUrls.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package github4s

case class GithubApiUrls(
baseUrl: String,
authorizeUrl: String,
accessTokenUrl: String
)

object GithubDefaultUrls {

implicit val defaultUrls: GithubApiUrls = GithubApiUrls(
"https://api.github.com/",
"https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&scope=%s&state=%s",
"https://github.com/login/oauth/access_token"
)
}
2 changes: 1 addition & 1 deletion src/main/scala/github4s/GithubResponses.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ object GithubResponses {

type GHResponse[A] = GHException Xor GHResult[A]

case class GHResult[A](value: A, statusCode: Int, headers: Map[String, IndexedSeq[String]])
case class GHResult[A](result: A, statusCode: Int, headers: Map[String, IndexedSeq[String]])

sealed abstract class GHException(msg: String, cause: Option[Throwable] = None) extends Throwable(msg) {
cause foreach initCause
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/github4s/HttpClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import github4s.free.domain.Pagination
import io.circe.Decoder
import scalaj.http._

class HttpClient(implicit config: GithubApiConfig) {
class HttpClient(implicit urls: GithubApiUrls) {

val defaultPagination = Pagination(1, 1000)

Expand Down Expand Up @@ -148,6 +148,6 @@ class HttpClient(implicit config: GithubApiConfig) {
val defaultPage: Int = 1
val defaultPerPage: Int = 30

private def buildURL(method: String) = config.getString("github.baseUrl") + method
private def buildURL(method: String) = urls.baseUrl + method

}
60 changes: 60 additions & 0 deletions src/main/scala/github4s/Implicits.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package github4s

import cats.std.FutureInstances
import cats.std.future._
import cats.{ Monad, Id, Eval, MonadError }
import github4s.free.interpreters.Interpreters
import scala.concurrent.{ ExecutionContext, Future }

object implicits extends Interpreters with EvalInstances with IdInstances with FutureInstances

trait EvalInstances {

implicit val evalMonadError: MonadError[Eval, Throwable] = new MonadError[Eval, Throwable] {

override def pure[A](x: A): Eval[A] = Eval.now(x)

override def map[A, B](fa: Eval[A])(f: A B): Eval[B] = fa.map(f)

override def flatMap[A, B](fa: Eval[A])(ff: A Eval[B]): Eval[B] =
fa.flatMap(ff)

override def raiseError[A](e: Throwable): Eval[A] =
Eval.later({ throw e })

override def handleErrorWith[A](fa: Eval[A])(f: Throwable Eval[A]): Eval[A] =
Eval.later({
try {
fa.value
} catch {
case e: Throwable f(e).value
}
})
}

}

trait IdInstances {

implicit def idMonadError(implicit I: Monad[Id]): MonadError[Id, Throwable] = new MonadError[Id, Throwable] {

override def pure[A](x: A): Id[A] = I.pure(x)

override def ap[A, B](ff: Id[A B])(fa: Id[A]): Id[B] = I.ap(ff)(fa)

override def map[A, B](fa: Id[A])(f: Id[A B]): Id[B] = I.map(fa)(f)

override def flatMap[A, B](fa: Id[A])(f: A Id[B]): Id[B] = I.flatMap(fa)(f)

override def raiseError[A](e: Throwable): Id[A] = throw e

override def handleErrorWith[A](fa: Id[A])(f: Throwable Id[A]): Id[A] = {
try {
fa
} catch {
case e: Exception f(e)
}
}
}

}
10 changes: 5 additions & 5 deletions src/main/scala/github4s/api/Auth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import java.util.UUID
import cats.data.Xor
import github4s.GithubResponses.{ GHResult, GHResponse }
import github4s.free.domain._
import github4s.{ GithubApiConfig, HttpClient }
import github4s.{ GithubApiUrls, HttpClient }
import io.circe.generic.auto._
import io.circe.syntax._
import scalaj.http.HttpConstants._

/** Factory to encapsulate calls related to Auth operations */
class Auth(implicit config: GithubApiConfig) {
class Auth(implicit urls: GithubApiUrls) {

val httpClient = new HttpClient

val authorizeUrl = config.getString("github.authorizeUrl")
val accessTokenUrl = config.getString("github.accessTokenUrl")
val authorizeUrl = urls.authorizeUrl
val accessTokenUrl = urls.accessTokenUrl

/**
* Call to request a new authorization given a basic authentication, the returned object Authorization includes an
Expand Down Expand Up @@ -59,7 +59,7 @@ class Auth(implicit config: GithubApiConfig) {
val state = UUID.randomUUID().toString
Xor.Right(
GHResult(
value = Authorize(authorizeUrl.format(client_id, redirect_uri, scopes.mkString(","), state), state),
result = Authorize(authorizeUrl.format(client_id, redirect_uri, scopes.mkString(","), state), state),
statusCode = 200,
headers = Map.empty
)
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/github4s/api/Repos.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package github4s.api

import github4s.GithubResponses.GHResponse
import github4s.free.domain.{ Pagination, Commit, Repository, User }
import github4s.{ GithubApiConfig, Decoders, HttpClient }
import github4s.{ GithubApiUrls, Decoders, HttpClient }
import io.circe.generic.auto._

/** Factory to encapsulate calls related to Repositories operations */
class Repos(implicit config: GithubApiConfig) {
class Repos(implicit urls: GithubApiUrls) {

import Decoders._

Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/github4s/api/Users.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package github4s.api

import github4s.GithubResponses.GHResponse
import github4s.{ GithubApiConfig, HttpClient }
import github4s.{ GithubApiUrls, HttpClient }
import github4s.free.domain.{ Pagination, User }
import io.circe.generic.auto._

/** Factory to encapsulate calls related to Users operations */
class Users(implicit config: GithubApiConfig) {
class Users(implicit urls: GithubApiUrls) {

val httpClient = new HttpClient

Expand Down

0 comments on commit 6cc84e9

Please sign in to comment.