Skip to content

Commit 6be92dc

Browse files
author
Rafa Paradela
committed
Wip
1 parent f1bd937 commit 6be92dc

13 files changed

Lines changed: 215 additions & 59 deletions

File tree

build.sbt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1+
organization := "com.fortysevendeg"
2+
13
name := "github4s"
24

3-
version := "1.0"
5+
version := "0.1"
46

57
scalaVersion := "2.11.7"
68

79
libraryDependencies ++= Seq(
810
"org.typelevel" %% "cats" % "0.4.0",
11+
"org.scalaz" %% "scalaz-concurrent" % "7.1.4",
912
"org.scalaj" %% "scalaj-http" % "2.2.1",
1013
"io.circe" %% "circe-core" % "0.3.0",
1114
"io.circe" %% "circe-generic" % "0.3.0",
Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,31 @@
11
package com.fortysevendeg.github4s
22

33
import cats.data.{OptionT, XorT}
4-
import cats.{ApplicativeError, Monad, ~>}
4+
import cats.{MonadError, ~>}
55
import com.fortysevendeg.github4s.GithubResponses._
66
import com.fortysevendeg.github4s.app._
77

88

9-
case class GithubConfig(accessToken : Option[String] = None, extraHeaders : Map[String, String] = Map.empty:Map[String, String])
9+
case class GithubConfig(accessToken : Option[String] = None)
1010

1111

12-
object Github {
12+
class Github(accessToken : Option[String] = None) {
13+
14+
lazy val users = new GHUsers(accessToken)
15+
lazy val repos = new GHRepos(accessToken)
16+
lazy val auth = new GHAuth(accessToken)
17+
18+
}
1319

14-
lazy val users = new GHUsers()
15-
lazy val repos = new GHRepos()
16-
lazy val auth = new GHAuth()
20+
object Github {
1721

22+
def apply(accessToken: Option[String] = None) = new Github(accessToken)
1823

1924
implicit class GithubIOSyntaxXOR[A](gio : GHIO[GHResponse[A]]) {
2025

21-
def exec[M[_]](implicit C : GithubConfig, M: Monad[M], I : (GitHub4s ~> M), A: ApplicativeError[M, Throwable]): M[GHResponse[A]] = gio foldMap I
26+
def exec[M[_]](implicit I : (GitHub4s ~> M), A: MonadError[M, Throwable]): M[GHResponse[A]] = gio foldMap I
2227

2328
def liftGH: XorT[GHIO, GHException, GHResult[A]] = XorT[GHIO, GHException, GHResult[A]](gio)
2429

2530
}
26-
27-
2831
}

src/main/scala/com/fortysevendeg/github4s/GithubAPIs.scala

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,22 @@ import com.fortysevendeg.github4s.app._
55
import com.fortysevendeg.github4s.free.algebra.{AuthOps, RepositoryOps, UserOps}
66
import com.fortysevendeg.github4s.free.domain._
77

8-
class GHUsers(implicit O : UserOps[GitHub4s]){
8+
class GHUsers(accessToken : Option[String] = None)(implicit O : UserOps[GitHub4s]){
99

10-
def get(username : String): GHIO[GHResponse[Collaborator]] = O.getUser(username)
10+
def get(username : String): GHIO[GHResponse[Collaborator]] = O.getUser(username, accessToken)
1111

12-
def getAuth: GHIO[GHResponse[Collaborator]] = O.getAuthUser
12+
def getAuth: GHIO[GHResponse[Collaborator]] = O.getAuthUser(accessToken)
1313

14-
def getUsers(since : Int, pagination: Option[Pagination] = None): GHIO[GHResponse[List[Collaborator]]] = O.getUsers(since, pagination)
14+
def getUsers(since: Int, pagination: Option[Pagination] = None): GHIO[GHResponse[List[Collaborator]]] = O.getUsers(since, pagination, accessToken)
1515

1616
}
1717

18-
class GHRepos(implicit O : RepositoryOps[GitHub4s]){
18+
class GHRepos(accessToken : Option[String] = None)(implicit O : RepositoryOps[GitHub4s]){
1919

2020
def get(owner : String, repo: String): GHIO[GHResponse[Repository]] = O.getRepo(owner, repo)
2121

2222
def listCommits(
23-
owner : String,
23+
owner: String,
2424
repo: String,
2525
sha: Option[String] = None,
2626
path: Option[String] = None,
@@ -32,7 +32,7 @@ class GHRepos(implicit O : RepositoryOps[GitHub4s]){
3232

3333
}
3434

35-
class GHAuth(implicit O : AuthOps[GitHub4s]){
35+
class GHAuth(accessToken : Option[String] = None)(implicit O : AuthOps[GitHub4s]){
3636

3737
def newAuth(
3838
username: String,

src/main/scala/com/fortysevendeg/github4s/GithubResponses.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ object GithubResponses {
2424
final case class GHListResult[A](value: A, statusCode: Int, headers: Map[String, IndexedSeq[String]], decoder: Decoder[A]) extends GHResult[A]
2525

2626

27-
sealed abstract class GHException(msg : String, cause : Option[Throwable] = None) extends Exception(msg) {
27+
sealed abstract class GHException(msg : String, cause : Option[Throwable] = None) extends Throwable(msg) {
2828
cause foreach initCause
2929
}
3030

src/main/scala/com/fortysevendeg/github4s/HttpClient.scala

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -79,42 +79,44 @@ class HttpClient {
7979
}
8080

8181
def get[A](
82+
accessToken: Option[String] = None,
8283
method: String,
8384
params: Map[String, String] = Map.empty,
8485
pagination: Option[Pagination] = None)
85-
(implicit C: GithubConfig, D: Decoder[A]): GHResponse[A] =
86+
(implicit D: Decoder[A]): GHResponse[A] =
8687
GithubResponses.toEntity(HttpRequestBuilder(buildURL(method))
87-
.withAuth(C.accessToken)
88+
.withAuth(accessToken)
8889
.withParams(params ++ pagination.fold(Map.empty[String, String])(p => Map("page" -> p.page.toString, "per_page" -> p.per_page.toString)))
8990
.run, D)
9091

91-
def getByUrl[A](url: String, d: Decoder[A])(implicit C: GithubConfig): GHResponse[A] =
92+
def getByUrl[A](accessToken: Option[String] = None, url: String, d: Decoder[A]): GHResponse[A] =
9293
GithubResponses.toEntity(HttpRequestBuilder(url)
93-
.withAuth(C.accessToken)
94+
.withAuth(accessToken)
9495
.run, d)
9596

9697

97-
def patch[A](method: String, data: String)(implicit C: GithubConfig, D: Decoder[A]): GHResponse[A] =
98+
def patch[A](accessToken: Option[String] = None, method: String, data: String)(implicit D: Decoder[A]): GHResponse[A] =
9899
GithubResponses.toEntity(HttpRequestBuilder(buildURL(method))
99100
.patchMethod
100-
.withAuth(C.accessToken)
101+
.withAuth(accessToken)
101102
.withData(data)
102103
.run, D)
103104

104-
def put(method: String)(implicit C: GithubConfig): GHResponse[Unit] =
105+
def put(accessToken: Option[String] = None, method: String): GHResponse[Unit] =
105106
GithubResponses.toEmpty(HttpRequestBuilder(buildURL(method))
106107
.putMethod
107-
.withAuth(C.accessToken)
108+
.withAuth(accessToken)
108109
.withHeaders(Map("Content-Length" -> "0"))
109110
.run)
110111

111112
def post[A](
113+
accessToken: Option[String] = None,
112114
method: String,
113115
headers: Map[String, String] = Map.empty,
114116
data: String)
115-
(implicit C: GithubConfig, D: Decoder[A]): GHResponse[A] =
117+
(implicit D: Decoder[A]): GHResponse[A] =
116118
GithubResponses.toEntity(HttpRequestBuilder(buildURL(method))
117-
.withAuth(C.accessToken)
119+
.withAuth(accessToken)
118120
.withHeaders(headers)
119121
.withData(data)
120122
.run, D)
@@ -138,10 +140,10 @@ class HttpClient {
138140
.withData(data)
139141
.run, D)
140142

141-
def delete(method: String)(implicit C: GithubConfig): GHResponse[Unit] =
143+
def delete(accessToken: Option[String] = None, method: String): GHResponse[Unit] =
142144
GithubResponses.toEmpty(HttpRequestBuilder(buildURL(method))
143145
.deleteMethod
144-
.withAuth(C.accessToken)
146+
.withAuth(accessToken)
145147
.run)
146148

147149

src/main/scala/com/fortysevendeg/github4s/api/Repos.scala

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,21 @@ object Repos {
99

1010
protected val httpClient = new HttpClient()
1111

12-
def get(owner: String, repo: String)(implicit C : GithubConfig): GHResponse[Repository] =
13-
httpClient.get[Repository](s"repos/$owner/$repo")
12+
def get(accessToken: Option[String] = None, owner: String, repo: String): GHResponse[Repository] =
13+
httpClient.get[Repository](accessToken, s"repos/$owner/$repo")
1414

1515
def listCommits(
16+
accessToken: Option[String] = None,
1617
owner: String,
1718
repo: String,
1819
sha: Option[String] = None,
1920
path: Option[String] = None,
2021
author: Option[String] = None,
2122
since: Option[String] = None,
2223
until: Option[String] = None,
23-
pagination: Option[Pagination] = None)(implicit C : GithubConfig): GHResponse[List[Commit]] =
24+
pagination: Option[Pagination] = None): GHResponse[List[Commit]] =
2425

25-
httpClient.get[List[Commit]](s"repos/$owner/$repo/commits", Map(
26+
httpClient.get[List[Commit]](accessToken, s"repos/$owner/$repo/commits", Map(
2627
"sha" -> sha,
2728
"path" -> path,
2829
"author" -> author,

src/main/scala/com/fortysevendeg/github4s/free/algebra/RequestOps.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import io.circe.Decoder
77
/** Requests ops ADT
88
*/
99
sealed trait RequestOp[A]
10-
final case class Next[A](url: String, decoder: Decoder[A]) extends RequestOp[GHResponse[A]]
10+
final case class Next[A](url: String, decoder: Decoder[A], accessToken: Option[String] = None) extends RequestOp[GHResponse[A]]
1111

1212

1313
/** Exposes Requests operations as a Free monadic algebra that may be combined with other Algebras via
1414
* Coproduct
1515
*/
1616
class RequestOps[F[_]](implicit I: Inject[RequestOp, F]) {
1717

18-
def next[A](url: String, decoder: Decoder[A]): Free[F, GHResponse[A]] = Free.inject[RequestOp, F](Next[A](url, decoder))
18+
def next[A](url: String, decoder: Decoder[A], accessToken: Option[String] = None): Free[F, GHResponse[A]] = Free.inject[RequestOp, F](Next[A](url, decoder, accessToken))
1919

2020
def nextList[A](result: GHListResult[A]): Option[Free[F, GHResponse[A]]] = followLink[A](result, "next")
2121

src/main/scala/com/fortysevendeg/github4s/free/algebra/UserOps.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@ import com.fortysevendeg.github4s.free.domain.{Pagination, Collaborator}
77
/** Users ops ADT
88
*/
99
sealed trait UserOp[A]
10-
final case class GetUser(username: String) extends UserOp[GHResponse[Collaborator]]
11-
final case class GetAuthUser() extends UserOp[GHResponse[Collaborator]]
12-
final case class GetUsers(since: Int, pagination: Option[Pagination] = None) extends UserOp[GHResponse[List[Collaborator]]]
10+
final case class GetUser(username: String, accessToken: Option[String] = None) extends UserOp[GHResponse[Collaborator]]
11+
final case class GetAuthUser(accessToken: Option[String] = None) extends UserOp[GHResponse[Collaborator]]
12+
final case class GetUsers(since: Int, pagination: Option[Pagination] = None, accessToken: Option[String] = None) extends UserOp[GHResponse[List[Collaborator]]]
1313

1414

1515
/** Exposes Users operations as a Free monadic algebra that may be combined with other Algebras via
1616
* Coproduct
1717
*/
1818
class UserOps[F[_]](implicit I: Inject[UserOp, F]) {
1919

20-
def getUser(username: String): Free[F, GHResponse[Collaborator]] = Free.inject[UserOp, F](GetUser(username))
20+
def getUser(username: String, accessToken: Option[String] = None): Free[F, GHResponse[Collaborator]] = Free.inject[UserOp, F](GetUser(username, accessToken))
2121

22-
def getAuthUser: Free[F, GHResponse[Collaborator]] = Free.inject[UserOp, F](GetAuthUser())
22+
def getAuthUser(accessToken: Option[String] = None): Free[F, GHResponse[Collaborator]] = Free.inject[UserOp, F](GetAuthUser(accessToken))
2323

24-
def getUsers(since: Int, pagination: Option[Pagination] = None): Free[F, GHResponse[List[Collaborator]]] = Free.inject[UserOp, F](GetUsers(since, pagination))
24+
def getUsers(since: Int, pagination: Option[Pagination] = None, accessToken: Option[String] = None): Free[F, GHResponse[List[Collaborator]]] = Free.inject[UserOp, F](GetUsers(since, pagination, accessToken))
2525

2626
}
2727

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.fortysevendeg.github4s.free.interpreters
2+
3+
import cats.{Monad, MonadError}
4+
5+
trait IdInstances {
6+
implicit def idMonadError(
7+
implicit
8+
I: Monad[cats.Id]
9+
): MonadError[cats.Id, Throwable] = new MonadError[cats.Id, Throwable] {
10+
11+
import cats.Id
12+
13+
override def pure[A](x: A): Id[A] = I.pure(x)
14+
15+
override def ap[A, B](ff: Id[A B])(fa: Id[A]): Id[B] = I.ap(ff)(fa)
16+
17+
override def map[A, B](fa: Id[A])(f: Id[A B]): Id[B] = I.map(fa)(f)
18+
19+
override def flatMap[A, B](fa: Id[A])(f: A Id[B]): Id[B] = I.flatMap(fa)(f)
20+
21+
override def product[A, B](fa: Id[A], fb: Id[B]): Id[(A, B)] = I.product(fa, fb)
22+
23+
override def raiseError[A](e: Throwable): Id[A] =
24+
throw e
25+
26+
override def handleErrorWith[A](fa: Id[A])(f: Throwable Id[A]): Id[A] = {
27+
try {
28+
fa
29+
} catch {
30+
case e: Exception f(e)
31+
}
32+
}
33+
}
34+
}
35+
object IdInterpreters extends Interpreters[cats.Id] with IdInstances

src/main/scala/com/fortysevendeg/github4s/free/interpreters/Interpreters.scala

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
package com.fortysevendeg.github4s.free.interpreters
22

3-
import cats.{ApplicativeError, ~>, Eval}
3+
import cats.{MonadError, ApplicativeError, ~>, Eval}
44
import com.fortysevendeg.github4s.{HttpClient, GithubConfig}
55
import com.fortysevendeg.github4s.api.{Auth, Repos}
6-
import com.fortysevendeg.github4s.app.{C01, C02, GitHub4s}
6+
import com.fortysevendeg.github4s.app.{COGH01, COGH02, GitHub4s}
77
import com.fortysevendeg.github4s.free.algebra._
88
import io.circe.Decoder
99

1010
trait Interpreters[M[_]] {
1111

1212
implicit def interpreters(
1313
implicit
14-
A: ApplicativeError[M, Throwable],
15-
C : GithubConfig
14+
A: MonadError[M, Throwable]
1615
): GitHub4s ~> M = {
17-
val repositoryAndUserInterpreter: C01 ~> M = repositoryOpsInterpreter or userOpsInterpreter
18-
val c01nterpreter: C02 ~> M = requestOpsInterpreter or repositoryAndUserInterpreter
16+
val repositoryAndUserInterpreter: COGH01 ~> M = repositoryOpsInterpreter or userOpsInterpreter
17+
val c01nterpreter: COGH02 ~> M = requestOpsInterpreter or repositoryAndUserInterpreter
1918
val all: GitHub4s ~> M = authOpsInterpreter or c01nterpreter
2019
all
2120
}
@@ -25,29 +24,29 @@ trait Interpreters[M[_]] {
2524

2625
/** Lifts Repository Ops to an effect capturing Monad such as Task via natural transformations
2726
*/
28-
def repositoryOpsInterpreter(implicit A: ApplicativeError[M, Throwable], C : GithubConfig): RepositoryOp ~> M = new (RepositoryOp ~> M) {
27+
def repositoryOpsInterpreter(implicit A: ApplicativeError[M, Throwable]): RepositoryOp ~> M = new (RepositoryOp ~> M) {
2928
def apply[A](fa: RepositoryOp[A]): M[A] = fa match {
30-
case GetRepo(owner, repo) A.pureEval(Eval.later(Repos.get(owner, repo)))
31-
case ListCommits(owner, repo, sha, path, author, since, until, pagination) A.pureEval(Eval.later(Repos.listCommits(owner, repo, sha, path, author, since, until, pagination)))
29+
case GetRepo(owner, repo, accessToken) A.pureEval(Eval.later(Repos.get(accessToken, owner, repo)))
30+
case ListCommits(owner, repo, sha, path, author, since, until, pagination, accessToken) A.pureEval(Eval.later(Repos.listCommits(accessToken, owner, repo, sha, path, author, since, until, pagination)))
3231
}
3332
}
3433

3534
/** Lifts User Ops to an effect capturing Monad such as Task via natural transformations
3635
*/
37-
def userOpsInterpreter(implicit A: ApplicativeError[M, Throwable], C : GithubConfig): UserOp ~> M = new (UserOp ~> M) {
36+
def userOpsInterpreter(implicit A: ApplicativeError[M, Throwable]): UserOp ~> M = new (UserOp ~> M) {
3837

3938
import com.fortysevendeg.github4s.api.Users
4039

4140
def apply[A](fa: UserOp[A]): M[A] = fa match {
42-
case GetUser(username) A.pureEval(Eval.later(Users.get(username)))
43-
case GetAuthUser() A.pureEval(Eval.later(Users.getAuth))
44-
case GetUsers(since, pagination) A.pureEval(Eval.later(Users.getUsers(since, pagination)))
41+
case GetUser(username, accessToken) A.pureEval(Eval.later(Users.get(accessToken, username)))
42+
case GetAuthUser(accessToken) A.pureEval(Eval.later(Users.getAuth(accessToken)))
43+
case GetUsers(since, pagination, accessToken) A.pureEval(Eval.later(Users.getUsers(accessToken, since, pagination)))
4544
}
4645
}
4746

4847
/** Lifts Auth Ops to an effect capturing Monad such as Task via natural transformations
4948
*/
50-
def authOpsInterpreter(implicit A: ApplicativeError[M, Throwable], C : GithubConfig): AuthOp ~> M = new (AuthOp ~> M) {
49+
def authOpsInterpreter(implicit A: ApplicativeError[M, Throwable]): AuthOp ~> M = new (AuthOp ~> M) {
5150
def apply[A](fa: AuthOp[A]): M[A] = fa match {
5251
case NewAuth(username, password, scopes, note, client_id, client_secret) A.pureEval(Eval.later(Auth.newAuth(username, password, scopes, note, client_id, client_secret)))
5352
case AuthorizeUrl(client_id, redirect_uri, scopes) => A.pureEval(Eval.later(Auth.authorizeUrl(client_id, redirect_uri, scopes)))
@@ -57,11 +56,11 @@ trait Interpreters[M[_]] {
5756

5857
/** Lifts Request Ops to an effect capturing Monad such as Task via natural transformations
5958
*/
60-
def requestOpsInterpreter(implicit App: ApplicativeError[M, Throwable], C : GithubConfig): RequestOp ~> M = new (RequestOp ~> M) {
59+
def requestOpsInterpreter(implicit App: ApplicativeError[M, Throwable]): RequestOp ~> M = new (RequestOp ~> M) {
6160
def apply[A](fa: RequestOp[A]): M[A] = fa match {
62-
case Next(url: String, decoder: Decoder[A]) {
61+
case Next(url: String, decoder: Decoder[A], accessToken) {
6362
//implicit val d: Decoder[A] = decoder
64-
App.pureEval(Eval.later(httpClient.getByUrl(url, decoder)))
63+
App.pureEval(Eval.later(httpClient.getByUrl(accessToken, url, decoder)))
6564
}
6665
}
6766
}

0 commit comments

Comments
 (0)