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

Introduces custom GitHub configuration #384

Merged
merged 9 commits into from
Mar 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,30 @@ object ProgramEvalWithHeaders {
}
```

## Using github4s with GitHub Enterprise

By default `Github` instances are configured for the [public GitHub][public-github] endpoints via a fallback
`GithubConfig` instance which is picked up by the `Github` constructor if there's no other `GithubConfig` in the scope.

It is also possible to pass a custom GitHub configuration (e.g. for a particular [GitHub Enterprise][github-enterprise]
server). To override the default configuration values declare a custom `GithubConfig` instance in an appropriate
scope:
```scala mdoc:silent
import github4s.{Github, GithubConfig}

implicit val config = GithubConfig(
baseUrl = "", // default: "https://api.github.com/"
authorizeUrl = "", // default: "https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&scope=%s&state=%s"
accessTokenUrl = "" // default: "https://github.com/login/oauth/access_token"
)

val github = Github[IO](httpClient, None)
```
Please refer your GitHub Enterprise server docs for exact URL values for `baseUrl`, `authorizeUrl` and `accessTokenUrl`.

[access-token]: https://github.com/settings/tokens
[cats-sync]: https://typelevel.org/cats-effect/typeclasses/sync.html
[monix-task]: https://monix.io/docs/3x/eval/task.html
[http4s-client]: https://http4s.org/v0.21/client/
[public-github]: https://github.com
[github-enterprise]: https://github.com/enterprise
3 changes: 0 additions & 3 deletions github4s/src/main/resources/application.conf

This file was deleted.

8 changes: 3 additions & 5 deletions github4s/src/main/scala/github4s/Github.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ import org.http4s.client.Client
class Github[F[_]: Sync](
client: Client[F],
accessToken: Option[String]
) {
)(implicit config: GithubConfig) {

private lazy val module: GithubAPIs[F] =
new GithubAPIv3[F](client, accessToken)
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
Expand All @@ -47,7 +46,6 @@ object Github {
def apply[F[_]: Sync](
client: Client[F],
accessToken: Option[String] = None
): Github[F] =
)(implicit config: GithubConfig): Github[F] =
new Github[F](client, accessToken)

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@
* limitations under the License.
*/

package github4s.http
package github4s

case class GithubAPIv3Config(
baseUrl: String = "https://api.github.com/",
authorizeUrl: String =
"https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&scope=%s&state=%s",
accessTokenUrl: String = "https://github.com/login/oauth/access_token"
)
final case class GithubConfig(baseUrl: String, authorizeUrl: String, accessTokenUrl: String)

object GithubConfig {
implicit val default: GithubConfig =
GithubConfig(
baseUrl = "https://api.github.com/",
authorizeUrl =
"https://github.com/login/oauth/authorize?client_id=%s&redirect_uri=%s&scope=%s&state=%s",
accessTokenUrl = "https://github.com/login/oauth/access_token"
)
}
11 changes: 5 additions & 6 deletions github4s/src/main/scala/github4s/http/Http4sSyntax.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@

package github4s.http

import org.http4s._
import org.http4s.MediaType
import org.http4s.Headers
import io.circe.{Encoder, Json, Printer}
import github4s.GithubConfig
import io.circe.syntax._
import io.circe.{Encoder, Json, Printer}
import org.http4s._
import org.http4s.headers.`Content-Type`

object Http4sSyntax {
Expand Down Expand Up @@ -49,8 +48,8 @@ object Http4sSyntax {
(self.headers.map(kv => Header(kv._1, kv._2)) ++
self.authHeader.map(kv => Header(kv._1, kv._2))).toList

def toUri(urls: GithubAPIv3Config): Uri =
Uri.fromString(self.url).getOrElse(Uri.unsafeFromString(urls.baseUrl)) =?
def toUri(config: GithubConfig): Uri =
Uri.fromString(self.url).getOrElse(Uri.unsafeFromString(config.baseUrl)) =?
self.params.map(kv => (kv._1, List(kv._2)))

}
Expand Down
11 changes: 5 additions & 6 deletions github4s/src/main/scala/github4s/http/HttpClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,16 @@ package github4s.http
import cats.effect.Sync
import cats.syntax.either._
import cats.syntax.functor._
import io.circe.{Decoder, Encoder}
import github4s.GithubConfig
import github4s.GithubResponses.{GHResponse, JsonParsingException}
import github4s.domain.Pagination
import github4s.http.Http4sSyntax._
import io.circe.{Decoder, Encoder}
import org.http4s.Request
import org.http4s.circe.CirceEntityDecoder._
import org.http4s.client.Client

class HttpClient[F[_]: Sync](client: Client[F]) {

val urls: GithubAPIv3Config = GithubAPIv3Config()
class HttpClient[F[_]: Sync](client: Client[F], val config: GithubConfig) {

def get[Res: Decoder](
accessToken: Option[String] = None,
Expand Down Expand Up @@ -130,14 +129,14 @@ class HttpClient[F[_]: Sync](client: Client[F]) {
val defaultPage: Int = 1
val defaultPerPage: Int = 30

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

private def run[Req: Encoder, Res: Decoder](request: RequestBuilder[Req]): F[GHResponse[Res]] = {
client
.run(
Request[F]()
.withMethod(request.httpVerb)
.withUri(request.toUri(urls))
.withUri(request.toUri(config))
.withHeaders(request.toHeaderList: _*)
.withJsonBody(request.data)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class AuthInterpreter[F[_]: Applicative](
val result: GHResponse[Authorize] =
GHResponse(
result = Authorize(
client.urls.authorizeUrl.format(client_id, redirect_uri, scopes.mkString(","), state),
client.config.authorizeUrl.format(client_id, redirect_uri, scopes.mkString(","), state),
state
).asRight,
statusCode = 200,
Expand All @@ -74,7 +74,7 @@ class AuthInterpreter[F[_]: Applicative](
headers: Map[String, String] = Map()
): F[GHResponse[OAuthToken]] =
client.postOAuth[NewOAuthRequest, OAuthToken](
url = client.urls.accessTokenUrl,
url = client.config.accessTokenUrl,
headers = headers,
data = NewOAuthRequest(client_id, client_secret, code, redirect_uri, state)
)
Expand Down
10 changes: 7 additions & 3 deletions github4s/src/main/scala/github4s/modules/GithubAPIs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package github4s.modules

import cats.effect.Sync
import github4s.GithubConfig
import github4s.algebras._
import github4s.http.HttpClient
import github4s.interpreters._
Expand All @@ -36,10 +37,13 @@ sealed trait GithubAPIs[F[_]] {
def projects: Projects[F]
}

class GithubAPIv3[F[_]: Sync](client: Client[F], accessToken: Option[String] = None)
extends GithubAPIs[F] {
class GithubAPIv3[F[_]: Sync](
client: Client[F],
config: GithubConfig,
accessToken: Option[String] = None
) extends GithubAPIs[F] {

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

override val users: Users[F] = new UsersInterpreter[F]
Expand Down
2 changes: 1 addition & 1 deletion github4s/src/test/scala/github4s/unit/AuthSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class AuthSpec extends BaseSpec {
)

implicit val httpClientMock = httpClientMockPostOAuth[NewOAuthRequest, OAuthToken](
url = "https://github.com/login/oauth/access_token",
url = dummyUrls.accessTokenUrl,
req = request,
response = response
)
Expand Down
5 changes: 3 additions & 2 deletions github4s/src/test/scala/github4s/utils/BaseSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,23 @@
package github4s.utils

import cats.effect.IO
import github4s.GithubConfig
import github4s.GithubResponses.GHResponse
import github4s.domain.Pagination
import github4s.http.HttpClient
import io.circe.{Decoder, Encoder}
import org.http4s.client.Client
import org.scalamock.scalatest.MockFactory
import org.scalatest.matchers.should.Matchers
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

trait BaseSpec extends AnyFlatSpec with Matchers with TestData with MockFactory {

implicit val ec = scala.concurrent.ExecutionContext.Implicits.global
implicit val io = cats.effect.IO.contextShift(ec)

@com.github.ghik.silencer.silent("deprecated")
class HttpClientTest extends HttpClient[IO](mock[Client[IO]])
class HttpClientTest extends HttpClient[IO](mock[Client[IO]], implicitly[GithubConfig])

def httpClientMockGet[Out](
url: String,
Expand Down
4 changes: 2 additions & 2 deletions github4s/src/test/scala/github4s/utils/DummyGithubUrls.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@

package github4s.utils

import github4s.http.GithubAPIv3Config
import github4s.GithubConfig

trait DummyGithubUrls {

implicit val dummyUrls: GithubAPIv3Config = GithubAPIv3Config(
implicit val dummyUrls: GithubConfig = GithubConfig(
baseUrl = "http://127.0.0.1:9999/",
authorizeUrl = "http://127.0.0.1:9999/authorize?client_id=%s&redirect_uri=%s&scope=%s&state=%s",
accessTokenUrl = "http://127.0.0.1:9999/login/oauth/access_token"
Expand Down