Skip to content

Commit dbc6012

Browse files
Allow sending different auth headers to GitHub4s (#875)
* Allow sending different auth headers to GitHub4s * Adds mima and keeps compatibility on public API * Reoves unused import [skip ci] * Changes for improving compatibilty * Fixes auth * Removes comment block * scalafmt
1 parent 7589d64 commit dbc6012

23 files changed

Lines changed: 346 additions & 203 deletions

build.sbt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import ProjectPlugin.on
2+
import com.typesafe.tools.mima.core._
3+
4+
Global / onChangedBuildSource := ReloadOnSourceChanges
25

36
ThisBuild / organization := "com.47deg"
47

@@ -10,7 +13,12 @@ val allScalaVersions = scala2Versions :+ scala3Version
1013
ThisBuild / scalaVersion := scala213
1114
ThisBuild / crossScalaVersions := allScalaVersions
1215

13-
addCommandAlias("ci-test", "scalafmtCheckAll; scalafmtSbtCheck; mdoc; ++test")
16+
disablePlugins(MimaPlugin)
17+
18+
addCommandAlias(
19+
"ci-test",
20+
"scalafmtCheckAll; scalafmtSbtCheck; mimaReportBinaryIssues; mdoc; ++test"
21+
)
1422
addCommandAlias("ci-docs", "github; mdoc; headerCreateAll; publishMicrosite")
1523
addCommandAlias("ci-publish", "github; ci-release")
1624

@@ -19,6 +27,7 @@ publish / skip := true
1927
lazy val github4s = (crossProject(JSPlatform, JVMPlatform))
2028
.crossType(CrossType.Full)
2129
.withoutSuffixFor(JVMPlatform)
30+
.enablePlugins(MimaPlugin)
2231
.settings(coreDeps: _*)
2332
.jsSettings(
2433
// See the README for why this is necessary
@@ -30,7 +39,11 @@ lazy val github4s = (crossProject(JSPlatform, JVMPlatform))
3039
// Increase number of inlines, needed for circe semiauto derivation
3140
scalacOptions ++= on(3)(Seq("-Xmax-inlines", "48")).value.flatten,
3241
// Disable nonunit warning on tests
33-
Test / scalacOptions -= "-Wnonunit-statement"
42+
Test / scalacOptions -= "-Wnonunit-statement",
43+
mimaPreviousArtifacts := Set("com.47deg" %% "github4s" % "0.32.1"),
44+
mimaBinaryIssueFilters ++= Seq(
45+
ProblemFilters.exclude[IncompatibleMethTypeProblem]("github4s.http.HttpClient.this")
46+
)
3447
)
3548

3649
//////////
@@ -41,11 +54,13 @@ lazy val microsite: Project = project
4154
.dependsOn(github4s.jvm)
4255
.enablePlugins(MicrositesPlugin)
4356
.enablePlugins(ScalaUnidocPlugin)
57+
.disablePlugins(MimaPlugin)
4458
.settings(micrositeSettings: _*)
4559
.settings(publish / skip := true)
4660
.settings(ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(github4s.jvm, microsite))
4761

4862
lazy val documentation = project
4963
.enablePlugins(MdocPlugin)
64+
.disablePlugins(MimaPlugin)
5065
.settings(mdocOut := file("."))
5166
.settings(publish / skip := true)

github4s/shared/src/main/scala/github4s/Github.scala

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,21 @@
1616

1717
package github4s
1818

19-
import cats.effect.kernel.Concurrent
19+
import cats.effect.Concurrent
2020
import github4s.algebras._
2121
import github4s.interpreters.StaticAccessToken
22-
import github4s.modules._
2322
import org.http4s.client.Client
2423

24+
@deprecated("Use github4s.GithubClient instead", "0.33.0")
2525
class Github[F[_]: Concurrent](
2626
client: Client[F],
2727
accessToken: AccessToken[F]
2828
)(implicit config: GithubConfig)
29-
extends GithubAPIs[F] {
30-
31-
private lazy val module: GithubAPIs[F] = new GithubAPIv3[F](client, config, accessToken)
32-
33-
lazy val users: Users[F] = module.users
34-
lazy val repos: Repositories[F] = module.repos
35-
lazy val auth: Auth[F] = module.auth
36-
lazy val gists: Gists[F] = module.gists
37-
lazy val issues: Issues[F] = module.issues
38-
lazy val activities: Activities[F] = module.activities
39-
lazy val gitData: GitData[F] = module.gitData
40-
lazy val pullRequests: PullRequests[F] = module.pullRequests
41-
lazy val organizations: Organizations[F] = module.organizations
42-
lazy val teams: Teams[F] = module.teams
43-
lazy val projects: Projects[F] = module.projects
44-
lazy val search: Search[F] = module.search
45-
}
29+
extends GithubClient[F](client, AccessHeader.from(accessToken))
4630

4731
object Github {
4832

33+
@deprecated("Use github4s.GithubClient instead", "0.33.0")
4934
def apply[F[_]: Concurrent](
5035
client: Client[F],
5136
accessToken: Option[String] = None
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2016-2023 47 Degrees Open Source <https://www.47deg.com>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package github4s
18+
19+
import cats.effect.Concurrent
20+
import github4s.algebras._
21+
import github4s.interpreters.StaticAccessToken
22+
import github4s.modules._
23+
import org.http4s.client.Client
24+
25+
private[github4s] class GithubClient[F[_]: Concurrent](
26+
client: Client[F],
27+
authHeader: AccessHeader[F]
28+
)(implicit config: GithubConfig)
29+
extends GithubAPIs[F] {
30+
31+
private lazy val module: GithubAPIs[F] = new GithubAPIsV3[F](client, config, authHeader)
32+
33+
lazy val users: Users[F] = module.users
34+
lazy val repos: Repositories[F] = module.repos
35+
lazy val auth: Auth[F] = module.auth
36+
lazy val gists: Gists[F] = module.gists
37+
lazy val issues: Issues[F] = module.issues
38+
lazy val activities: Activities[F] = module.activities
39+
lazy val gitData: GitData[F] = module.gitData
40+
lazy val pullRequests: PullRequests[F] = module.pullRequests
41+
lazy val organizations: Organizations[F] = module.organizations
42+
lazy val teams: Teams[F] = module.teams
43+
lazy val projects: Projects[F] = module.projects
44+
lazy val search: Search[F] = module.search
45+
}
46+
47+
object GithubClient {
48+
49+
def apply[F[_]: Concurrent](
50+
client: Client[F],
51+
accessToken: Option[String] = None
52+
)(implicit config: GithubConfig): GithubAPIs[F] =
53+
new GithubClient[F](client, AccessHeader.from(new StaticAccessToken(accessToken)))
54+
55+
def apply[F[_]: Concurrent](
56+
client: Client[F],
57+
authHeader: AccessHeader[F]
58+
)(implicit config: GithubConfig): GithubAPIs[F] =
59+
new GithubClient[F](client, authHeader)
60+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package github4s.algebras
2+
3+
import github4s.GHResponse
4+
5+
trait AccessHeader[F[_]] {
6+
def withAccessHeader[T](f: Map[String, String] => F[GHResponse[T]]): F[GHResponse[T]]
7+
}
8+
9+
object AccessHeader {
10+
def from[F[_]](accessToken: AccessToken[F]): AccessHeader[F] = new AccessHeader[F] {
11+
override def withAccessHeader[T](f: Map[String, String] => F[GHResponse[T]]): F[GHResponse[T]] =
12+
accessToken.withAccessToken { token =>
13+
f(token.fold(Map.empty[String, String])(t => Map("Authorization" -> s"token $t")))
14+
}
15+
}
16+
}

github4s/shared/src/main/scala/github4s/algebras/AccessToken.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,5 @@ import github4s.GHResponse
2929
* https://docs.github.com/en/free-pro-team@latest/developers/apps/authenticating-with-github-apps
3030
*/
3131
trait AccessToken[F[_]] {
32-
3332
def withAccessToken[T](f: Option[String] => F[GHResponse[T]]): F[GHResponse[T]]
3433
}

github4s/shared/src/main/scala/github4s/http/HttpClient.scala

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import cats.effect.kernel.Concurrent
2222
import cats.syntax.all._
2323
import github4s.GHError._
2424
import github4s._
25-
import github4s.algebras.AccessToken
25+
import github4s.algebras.{AccessHeader, AccessToken}
2626
import github4s.domain.Pagination
2727
import github4s.http.Http4sSyntax._
2828
import io.circe.{Decoder, Encoder}
@@ -31,24 +31,24 @@ import org.http4s.circe.jsonOf
3131
import org.http4s.client.Client
3232
import org.http4s._
3333

34-
class HttpClient[F[_]: Concurrent](
34+
class HttpClient[F[_]: Concurrent] private (
3535
client: Client[F],
3636
val config: GithubConfig,
37-
accessTokens: AccessToken[F]
37+
accessHeader: AccessHeader[F]
3838
) {
3939
import HttpClient._
40-
import accessTokens._
40+
import accessHeader._
4141

4242
def get[Res: Decoder](
4343
method: String,
4444
headers: Map[String, String] = Map.empty,
4545
params: Map[String, String] = Map.empty,
4646
pagination: Option[Pagination] = None
4747
): F[GHResponse[Res]] =
48-
withAccessToken { accessToken =>
48+
withAccessHeader { authHeader =>
4949
run[Unit, Res](
5050
RequestBuilder(url = buildURL(method))
51-
.withAuth(accessToken)
51+
.withAuthHeader(authHeader)
5252
.withHeaders(headers)
5353
.withParams(
5454
params ++ pagination.fold(Map.empty[String, String])(p =>
@@ -62,9 +62,9 @@ class HttpClient[F[_]: Concurrent](
6262
url: String,
6363
headers: Map[String, String] = Map.empty
6464
): F[GHResponse[Unit]] =
65-
withAccessToken(accessToken =>
65+
withAccessHeader(authHeader =>
6666
runWithoutResponse[Unit](
67-
RequestBuilder(buildURL(url)).withHeaders(headers).withAuth(accessToken)
67+
RequestBuilder(buildURL(url)).withHeaders(headers).withAuthHeader(authHeader)
6868
)
6969
)
7070

@@ -73,10 +73,10 @@ class HttpClient[F[_]: Concurrent](
7373
headers: Map[String, String] = Map.empty,
7474
data: Req
7575
): F[GHResponse[Res]] =
76-
withAccessToken(accessToken =>
76+
withAccessHeader(authHeader =>
7777
run[Req, Res](
7878
RequestBuilder(buildURL(method)).patchMethod
79-
.withAuth(accessToken)
79+
.withAuthHeader(authHeader)
8080
.withHeaders(headers)
8181
.withData(data)
8282
)
@@ -87,10 +87,10 @@ class HttpClient[F[_]: Concurrent](
8787
headers: Map[String, String] = Map(),
8888
data: Req
8989
): F[GHResponse[Res]] =
90-
withAccessToken(accessToken =>
90+
withAccessHeader(authHeader =>
9191
run[Req, Res](
9292
RequestBuilder(buildURL(url)).putMethod
93-
.withAuth(accessToken)
93+
.withAuthHeader(authHeader)
9494
.withHeaders(headers)
9595
.withData(data)
9696
)
@@ -101,10 +101,10 @@ class HttpClient[F[_]: Concurrent](
101101
headers: Map[String, String] = Map.empty,
102102
data: Req
103103
): F[GHResponse[Res]] =
104-
withAccessToken(accessToken =>
104+
withAccessHeader(authHeader =>
105105
run[Req, Res](
106106
RequestBuilder(buildURL(url)).postMethod
107-
.withAuth(accessToken)
107+
.withAuthHeader(authHeader)
108108
.withHeaders(headers)
109109
.withData(data)
110110
)
@@ -132,20 +132,20 @@ class HttpClient[F[_]: Concurrent](
132132
url: String,
133133
headers: Map[String, String] = Map.empty
134134
): F[GHResponse[Unit]] =
135-
withAccessToken(accessToken =>
135+
withAccessHeader(authHeader =>
136136
run[Unit, Unit](
137-
RequestBuilder(buildURL(url)).deleteMethod.withHeaders(headers).withAuth(accessToken)
137+
RequestBuilder(buildURL(url)).deleteMethod.withHeaders(headers).withAuthHeader(authHeader)
138138
)
139139
)
140140

141141
def deleteWithResponse[Res: Decoder](
142142
url: String,
143143
headers: Map[String, String] = Map.empty
144144
): F[GHResponse[Res]] =
145-
withAccessToken(accessToken =>
145+
withAccessHeader(authHeader =>
146146
run[Unit, Res](
147147
RequestBuilder(buildURL(url)).deleteMethod
148-
.withAuth(accessToken)
148+
.withAuthHeader(authHeader)
149149
.withHeaders(headers)
150150
)
151151
)
@@ -155,10 +155,10 @@ class HttpClient[F[_]: Concurrent](
155155
headers: Map[String, String] = Map.empty,
156156
data: Req
157157
): F[GHResponse[Res]] =
158-
withAccessToken(accessToken =>
158+
withAccessHeader(authHeader =>
159159
run[Req, Res](
160160
RequestBuilder(buildURL(url)).deleteMethod
161-
.withAuth(accessToken)
161+
.withAuthHeader(authHeader)
162162
.withHeaders(headers)
163163
.withData(data)
164164
)
@@ -231,4 +231,18 @@ object HttpClient {
231231

232232
private def responseBody[F[_]: Concurrent](response: Response[F]): F[String] =
233233
response.bodyText.compile.foldMonoid
234+
235+
def apply[F[_]: Concurrent](
236+
client: Client[F],
237+
config: GithubConfig,
238+
accessToken: AccessToken[F]
239+
): HttpClient[F] =
240+
new HttpClient[F](client, config, AccessHeader.from(accessToken))
241+
242+
def apply[F[_]: Concurrent](
243+
client: Client[F],
244+
config: GithubConfig,
245+
accessHeader: AccessHeader[F]
246+
): HttpClient[F] =
247+
new HttpClient[F](client, config, accessHeader)
234248
}

github4s/shared/src/main/scala/github4s/http/RequestBuilder.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ case class RequestBuilder[Res](
4141

4242
def withData(data: Res): RequestBuilder[Res] = this.copy(data = Some(data))
4343

44+
def withAuthHeader(authHeader: Map[String, String]): RequestBuilder[Res] =
45+
this.copy(authHeader = authHeader)
46+
4447
def withAuth(accessToken: Option[String] = None): RequestBuilder[Res] =
4548
this.copy(authHeader = accessToken match {
4649
case Some(token) => Map("Authorization" -> s"token $token")
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package github4s.interpreters
2+
3+
import github4s.GHResponse
4+
import github4s.algebras.AccessHeader
5+
6+
case class StaticAccessHeader[F[_]](accessHeader: Map[String, String]) extends AccessHeader[F] {
7+
override def withAccessHeader[T](f: Map[String, String] => F[GHResponse[T]]): F[GHResponse[T]] =
8+
f(accessHeader)
9+
}
10+
11+
object StaticAccessHeader {
12+
def noHeader[F[_]] = new StaticAccessHeader[F](Map.empty)
13+
}

github4s/shared/src/main/scala/github4s/modules/GithubAPIs.scala

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,35 +19,18 @@ package github4s.modules
1919
import cats.effect.kernel.Concurrent
2020
import github4s.GithubConfig
2121
import github4s.algebras._
22-
import github4s.http.HttpClient
2322
import github4s.interpreters._
2423
import org.http4s.client.Client
2524

25+
@deprecated("Use github4s.modules.GithubAPIsV3 instead", "0.33.0")
2626
class GithubAPIv3[F[_]: Concurrent](
2727
client: Client[F],
2828
config: GithubConfig,
2929
accessToken: AccessToken[F]
30-
) extends GithubAPIs[F] {
31-
32-
implicit val httpClient: HttpClient[F] = new HttpClient[F](client, config, accessToken)
33-
34-
override val users: Users[F] = new UsersInterpreter[F]
35-
override val repos: Repositories[F] = new RepositoriesInterpreter[F]
36-
override val auth: Auth[F] = new AuthInterpreter[F]
37-
override val gists: Gists[F] = new GistsInterpreter[F]
38-
override val issues: Issues[F] = new IssuesInterpreter[F]
39-
override val activities: Activities[F] = new ActivitiesInterpreter[F]
40-
override val gitData: GitData[F] = new GitDataInterpreter[F]
41-
override val pullRequests: PullRequests[F] = new PullRequestsInterpreter[F]
42-
override val organizations: Organizations[F] = new OrganizationsInterpreter[F]
43-
override val teams: Teams[F] = new TeamsInterpreter[F]
44-
override val projects: Projects[F] = new ProjectsInterpreter[F]
45-
override val search: Search[F] = new SearchInterpreter[F]
46-
47-
}
30+
) extends GithubAPIsV3[F](client, config, AccessHeader.from(accessToken))
4831

4932
object GithubAPIv3 {
50-
33+
@deprecated("Use github4s.modules.GithubAPIsV3.noAuth instead", "0.33.0")
5134
def noAuth[F[_]: Concurrent](client: Client[F], config: GithubConfig): GithubAPIv3[F] =
5235
new GithubAPIv3[F](client, config, StaticAccessToken.noToken)
5336
}

0 commit comments

Comments
 (0)