Skip to content

Commit

Permalink
Allow sending different auth headers to GitHub4s (#875)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
fedefernandez authored Nov 20, 2023
1 parent 7589d64 commit dbc6012
Show file tree
Hide file tree
Showing 23 changed files with 346 additions and 203 deletions.
19 changes: 17 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import ProjectPlugin.on
import com.typesafe.tools.mima.core._

Global / onChangedBuildSource := ReloadOnSourceChanges

ThisBuild / organization := "com.47deg"

Expand All @@ -10,7 +13,12 @@ val allScalaVersions = scala2Versions :+ scala3Version
ThisBuild / scalaVersion := scala213
ThisBuild / crossScalaVersions := allScalaVersions

addCommandAlias("ci-test", "scalafmtCheckAll; scalafmtSbtCheck; mdoc; ++test")
disablePlugins(MimaPlugin)

addCommandAlias(
"ci-test",
"scalafmtCheckAll; scalafmtSbtCheck; mimaReportBinaryIssues; mdoc; ++test"
)
addCommandAlias("ci-docs", "github; mdoc; headerCreateAll; publishMicrosite")
addCommandAlias("ci-publish", "github; ci-release")

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

//////////
Expand All @@ -41,11 +54,13 @@ lazy val microsite: Project = project
.dependsOn(github4s.jvm)
.enablePlugins(MicrositesPlugin)
.enablePlugins(ScalaUnidocPlugin)
.disablePlugins(MimaPlugin)
.settings(micrositeSettings: _*)
.settings(publish / skip := true)
.settings(ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(github4s.jvm, microsite))

lazy val documentation = project
.enablePlugins(MdocPlugin)
.disablePlugins(MimaPlugin)
.settings(mdocOut := file("."))
.settings(publish / skip := true)
23 changes: 4 additions & 19 deletions github4s/shared/src/main/scala/github4s/Github.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,21 @@

package github4s

import cats.effect.kernel.Concurrent
import cats.effect.Concurrent
import github4s.algebras._
import github4s.interpreters.StaticAccessToken
import github4s.modules._
import org.http4s.client.Client

@deprecated("Use github4s.GithubClient instead", "0.33.0")
class Github[F[_]: Concurrent](
client: Client[F],
accessToken: AccessToken[F]
)(implicit config: GithubConfig)
extends GithubAPIs[F] {

private lazy val module: GithubAPIs[F] = new GithubAPIv3[F](client, config, accessToken)

lazy val users: Users[F] = module.users
lazy val repos: Repositories[F] = module.repos
lazy val auth: Auth[F] = module.auth
lazy val gists: Gists[F] = module.gists
lazy val issues: Issues[F] = module.issues
lazy val activities: Activities[F] = module.activities
lazy val gitData: GitData[F] = module.gitData
lazy val pullRequests: PullRequests[F] = module.pullRequests
lazy val organizations: Organizations[F] = module.organizations
lazy val teams: Teams[F] = module.teams
lazy val projects: Projects[F] = module.projects
lazy val search: Search[F] = module.search
}
extends GithubClient[F](client, AccessHeader.from(accessToken))

object Github {

@deprecated("Use github4s.GithubClient instead", "0.33.0")
def apply[F[_]: Concurrent](
client: Client[F],
accessToken: Option[String] = None
Expand Down
60 changes: 60 additions & 0 deletions github4s/shared/src/main/scala/github4s/GithubClient.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2016-2023 47 Degrees Open Source <https://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

import cats.effect.Concurrent
import github4s.algebras._
import github4s.interpreters.StaticAccessToken
import github4s.modules._
import org.http4s.client.Client

private[github4s] class GithubClient[F[_]: Concurrent](
client: Client[F],
authHeader: AccessHeader[F]
)(implicit config: GithubConfig)
extends GithubAPIs[F] {

private lazy val module: GithubAPIs[F] = new GithubAPIsV3[F](client, config, authHeader)

lazy val users: Users[F] = module.users
lazy val repos: Repositories[F] = module.repos
lazy val auth: Auth[F] = module.auth
lazy val gists: Gists[F] = module.gists
lazy val issues: Issues[F] = module.issues
lazy val activities: Activities[F] = module.activities
lazy val gitData: GitData[F] = module.gitData
lazy val pullRequests: PullRequests[F] = module.pullRequests
lazy val organizations: Organizations[F] = module.organizations
lazy val teams: Teams[F] = module.teams
lazy val projects: Projects[F] = module.projects
lazy val search: Search[F] = module.search
}

object GithubClient {

def apply[F[_]: Concurrent](
client: Client[F],
accessToken: Option[String] = None
)(implicit config: GithubConfig): GithubAPIs[F] =
new GithubClient[F](client, AccessHeader.from(new StaticAccessToken(accessToken)))

def apply[F[_]: Concurrent](
client: Client[F],
authHeader: AccessHeader[F]
)(implicit config: GithubConfig): GithubAPIs[F] =
new GithubClient[F](client, authHeader)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package github4s.algebras

import github4s.GHResponse

trait AccessHeader[F[_]] {
def withAccessHeader[T](f: Map[String, String] => F[GHResponse[T]]): F[GHResponse[T]]
}

object AccessHeader {
def from[F[_]](accessToken: AccessToken[F]): AccessHeader[F] = new AccessHeader[F] {
override def withAccessHeader[T](f: Map[String, String] => F[GHResponse[T]]): F[GHResponse[T]] =
accessToken.withAccessToken { token =>
f(token.fold(Map.empty[String, String])(t => Map("Authorization" -> s"token $t")))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,5 @@ import github4s.GHResponse
* https://docs.github.com/en/free-pro-team@latest/developers/apps/authenticating-with-github-apps
*/
trait AccessToken[F[_]] {

def withAccessToken[T](f: Option[String] => F[GHResponse[T]]): F[GHResponse[T]]
}
54 changes: 34 additions & 20 deletions github4s/shared/src/main/scala/github4s/http/HttpClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import cats.effect.kernel.Concurrent
import cats.syntax.all._
import github4s.GHError._
import github4s._
import github4s.algebras.AccessToken
import github4s.algebras.{AccessHeader, AccessToken}
import github4s.domain.Pagination
import github4s.http.Http4sSyntax._
import io.circe.{Decoder, Encoder}
Expand All @@ -31,24 +31,24 @@ import org.http4s.circe.jsonOf
import org.http4s.client.Client
import org.http4s._

class HttpClient[F[_]: Concurrent](
class HttpClient[F[_]: Concurrent] private (
client: Client[F],
val config: GithubConfig,
accessTokens: AccessToken[F]
accessHeader: AccessHeader[F]
) {
import HttpClient._
import accessTokens._
import accessHeader._

def get[Res: Decoder](
method: String,
headers: Map[String, String] = Map.empty,
params: Map[String, String] = Map.empty,
pagination: Option[Pagination] = None
): F[GHResponse[Res]] =
withAccessToken { accessToken =>
withAccessHeader { authHeader =>
run[Unit, Res](
RequestBuilder(url = buildURL(method))
.withAuth(accessToken)
.withAuthHeader(authHeader)
.withHeaders(headers)
.withParams(
params ++ pagination.fold(Map.empty[String, String])(p =>
Expand All @@ -62,9 +62,9 @@ class HttpClient[F[_]: Concurrent](
url: String,
headers: Map[String, String] = Map.empty
): F[GHResponse[Unit]] =
withAccessToken(accessToken =>
withAccessHeader(authHeader =>
runWithoutResponse[Unit](
RequestBuilder(buildURL(url)).withHeaders(headers).withAuth(accessToken)
RequestBuilder(buildURL(url)).withHeaders(headers).withAuthHeader(authHeader)
)
)

Expand All @@ -73,10 +73,10 @@ class HttpClient[F[_]: Concurrent](
headers: Map[String, String] = Map.empty,
data: Req
): F[GHResponse[Res]] =
withAccessToken(accessToken =>
withAccessHeader(authHeader =>
run[Req, Res](
RequestBuilder(buildURL(method)).patchMethod
.withAuth(accessToken)
.withAuthHeader(authHeader)
.withHeaders(headers)
.withData(data)
)
Expand All @@ -87,10 +87,10 @@ class HttpClient[F[_]: Concurrent](
headers: Map[String, String] = Map(),
data: Req
): F[GHResponse[Res]] =
withAccessToken(accessToken =>
withAccessHeader(authHeader =>
run[Req, Res](
RequestBuilder(buildURL(url)).putMethod
.withAuth(accessToken)
.withAuthHeader(authHeader)
.withHeaders(headers)
.withData(data)
)
Expand All @@ -101,10 +101,10 @@ class HttpClient[F[_]: Concurrent](
headers: Map[String, String] = Map.empty,
data: Req
): F[GHResponse[Res]] =
withAccessToken(accessToken =>
withAccessHeader(authHeader =>
run[Req, Res](
RequestBuilder(buildURL(url)).postMethod
.withAuth(accessToken)
.withAuthHeader(authHeader)
.withHeaders(headers)
.withData(data)
)
Expand Down Expand Up @@ -132,20 +132,20 @@ class HttpClient[F[_]: Concurrent](
url: String,
headers: Map[String, String] = Map.empty
): F[GHResponse[Unit]] =
withAccessToken(accessToken =>
withAccessHeader(authHeader =>
run[Unit, Unit](
RequestBuilder(buildURL(url)).deleteMethod.withHeaders(headers).withAuth(accessToken)
RequestBuilder(buildURL(url)).deleteMethod.withHeaders(headers).withAuthHeader(authHeader)
)
)

def deleteWithResponse[Res: Decoder](
url: String,
headers: Map[String, String] = Map.empty
): F[GHResponse[Res]] =
withAccessToken(accessToken =>
withAccessHeader(authHeader =>
run[Unit, Res](
RequestBuilder(buildURL(url)).deleteMethod
.withAuth(accessToken)
.withAuthHeader(authHeader)
.withHeaders(headers)
)
)
Expand All @@ -155,10 +155,10 @@ class HttpClient[F[_]: Concurrent](
headers: Map[String, String] = Map.empty,
data: Req
): F[GHResponse[Res]] =
withAccessToken(accessToken =>
withAccessHeader(authHeader =>
run[Req, Res](
RequestBuilder(buildURL(url)).deleteMethod
.withAuth(accessToken)
.withAuthHeader(authHeader)
.withHeaders(headers)
.withData(data)
)
Expand Down Expand Up @@ -231,4 +231,18 @@ object HttpClient {

private def responseBody[F[_]: Concurrent](response: Response[F]): F[String] =
response.bodyText.compile.foldMonoid

def apply[F[_]: Concurrent](
client: Client[F],
config: GithubConfig,
accessToken: AccessToken[F]
): HttpClient[F] =
new HttpClient[F](client, config, AccessHeader.from(accessToken))

def apply[F[_]: Concurrent](
client: Client[F],
config: GithubConfig,
accessHeader: AccessHeader[F]
): HttpClient[F] =
new HttpClient[F](client, config, accessHeader)
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ case class RequestBuilder[Res](

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

def withAuthHeader(authHeader: Map[String, String]): RequestBuilder[Res] =
this.copy(authHeader = authHeader)

def withAuth(accessToken: Option[String] = None): RequestBuilder[Res] =
this.copy(authHeader = accessToken match {
case Some(token) => Map("Authorization" -> s"token $token")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package github4s.interpreters

import github4s.GHResponse
import github4s.algebras.AccessHeader

case class StaticAccessHeader[F[_]](accessHeader: Map[String, String]) extends AccessHeader[F] {
override def withAccessHeader[T](f: Map[String, String] => F[GHResponse[T]]): F[GHResponse[T]] =
f(accessHeader)
}

object StaticAccessHeader {
def noHeader[F[_]] = new StaticAccessHeader[F](Map.empty)
}
23 changes: 3 additions & 20 deletions github4s/shared/src/main/scala/github4s/modules/GithubAPIs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,35 +19,18 @@ package github4s.modules
import cats.effect.kernel.Concurrent
import github4s.GithubConfig
import github4s.algebras._
import github4s.http.HttpClient
import github4s.interpreters._
import org.http4s.client.Client

@deprecated("Use github4s.modules.GithubAPIsV3 instead", "0.33.0")
class GithubAPIv3[F[_]: Concurrent](
client: Client[F],
config: GithubConfig,
accessToken: AccessToken[F]
) extends GithubAPIs[F] {

implicit val httpClient: HttpClient[F] = new HttpClient[F](client, config, accessToken)

override val users: Users[F] = new UsersInterpreter[F]
override val repos: Repositories[F] = new RepositoriesInterpreter[F]
override val auth: Auth[F] = new AuthInterpreter[F]
override val gists: Gists[F] = new GistsInterpreter[F]
override val issues: Issues[F] = new IssuesInterpreter[F]
override val activities: Activities[F] = new ActivitiesInterpreter[F]
override val gitData: GitData[F] = new GitDataInterpreter[F]
override val pullRequests: PullRequests[F] = new PullRequestsInterpreter[F]
override val organizations: Organizations[F] = new OrganizationsInterpreter[F]
override val teams: Teams[F] = new TeamsInterpreter[F]
override val projects: Projects[F] = new ProjectsInterpreter[F]
override val search: Search[F] = new SearchInterpreter[F]

}
) extends GithubAPIsV3[F](client, config, AccessHeader.from(accessToken))

object GithubAPIv3 {

@deprecated("Use github4s.modules.GithubAPIsV3.noAuth instead", "0.33.0")
def noAuth[F[_]: Concurrent](client: Client[F], config: GithubConfig): GithubAPIv3[F] =
new GithubAPIv3[F](client, config, StaticAccessToken.noToken)
}
Loading

0 comments on commit dbc6012

Please sign in to comment.