Skip to content

Commit

Permalink
47degrees#751: add support for code search API
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoulaj committed Aug 27, 2022
1 parent b4953b8 commit cd361a1
Show file tree
Hide file tree
Showing 15 changed files with 493 additions and 1 deletion.
12 changes: 12 additions & 0 deletions github4s/shared/src/main/scala/github4s/Decoders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ object Decoders {
source <- Decoder[Option[RepositoryBase]].at("source")
} yield Repository.fromBaseRepos(base, parent, source)

implicit val decodeRepositoryMinimal: Decoder[RepositoryMinimal] =
deriveDecoder[RepositoryMinimal]

implicit val decodePRStatus: Decoder[PullRequestReviewState] =
Decoder.decodeString.emap {
case PRRStateApproved.value => PRRStateApproved.asRight
Expand Down Expand Up @@ -412,4 +415,13 @@ object Decoders {
deriveDecoder[BranchUpdateResponse]
implicit val decodeCommitComparisonResponse: Decoder[CommitComparisonResponse] =
deriveDecoder[CommitComparisonResponse]

implicit val decodeSearchResultTextMatch: Decoder[SearchResultTextMatch] =
deriveDecoder[SearchResultTextMatch]
implicit val decodeSearchResultTextMatchLocation: Decoder[SearchResultTextMatchLocation] =
deriveDecoder[SearchResultTextMatchLocation]
implicit val decodeSearchCodeResult: Decoder[SearchCodeResult] =
deriveDecoder[SearchCodeResult]
implicit val decodeSearchCodeResultItem: Decoder[SearchCodeResultItem] =
deriveDecoder[SearchCodeResultItem]
}
11 changes: 11 additions & 0 deletions github4s/shared/src/main/scala/github4s/Encoders.scala
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,8 @@ object Encoders {
case None => repo
}
}
implicit val encodeRepositoryMinimal: Encoder[RepositoryMinimal] =
deriveEncoder[RepositoryMinimal]

implicit val encoderPublicGitHubEvents: Encoder[PublicGitHubEvent] =
Encoder.instance { e =>
Expand Down Expand Up @@ -282,4 +284,13 @@ object Encoders {
}
implicit val encodeCommitComparisonResponse: Encoder[CommitComparisonResponse] =
deriveEncoder[CommitComparisonResponse]

implicit val encodeSearchResultTextMatch: Encoder[SearchResultTextMatch] =
deriveEncoder[SearchResultTextMatch]
implicit val encodeSearchResultTextMatchLocation: Encoder[SearchResultTextMatchLocation] =
deriveEncoder[SearchResultTextMatchLocation]
implicit val encodeSearchCodeResult: Encoder[SearchCodeResult] =
deriveEncoder[SearchCodeResult]
implicit val encodeSearchCodeResultItem: Encoder[SearchCodeResultItem] =
deriveEncoder[SearchCodeResultItem]
}
1 change: 1 addition & 0 deletions github4s/shared/src/main/scala/github4s/Github.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class Github[F[_]: Concurrent](
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 Github {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,9 @@ trait GithubAPIs[F[_]] {
* Project-related operations.
*/
def projects: Projects[F]

/**
* Search-related operations.
*/
def search: Search[F]
}
42 changes: 42 additions & 0 deletions github4s/shared/src/main/scala/github4s/algebras/Search.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2016-2022 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.algebras

import github4s.GHResponse
import github4s.domain._

trait Search[F[_]] {

/**
* Search code
*
* @param query query string
* @param searchParams search parameters
* @param textMatches enable text matches
* @param pagination Limit and Offset for pagination
* @param headers optional user headers to include in the request
* @return GHResponse[SearchCodeResult] the search results
*/
def searchCode(
query: String,
searchParams: List[SearchCodeParam],
textMatches: Boolean = false,
pagination: Option[Pagination] = None,
headers: Map[String, String] = Map()
): F[GHResponse[SearchCodeResult]]

}
48 changes: 48 additions & 0 deletions github4s/shared/src/main/scala/github4s/domain/Repository.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,54 @@ final case class RepositoryBase(
topics: List[String] = Nil
)

/** A `Repository` but with some information omitted (this is used by the search API) */
final case class RepositoryMinimal(
id: Long,
node_id: String,
name: String,
full_name: String,
`private`: Boolean,
owner: User,
html_url: String,
description: Option[String] = None,
fork: Boolean,
url: String,
forks_url: String,
keys_url: String,
collaborators_url: String,
teams_url: String,
hooks_url: String,
issue_events_url: String,
events_url: String,
assignees_url: String,
branches_url: String,
blobs_url: String,
git_tags_url: String,
git_refs_url: String,
trees_url: String,
statuses_url: String,
languages_url: String,
stargazers_url: String,
contributors_url: String,
subscribers_url: String,
subscription_url: String,
commits_url: String,
git_commits_url: String,
comments_url: String,
issue_comment_url: String,
contents_url: String,
compare_url: String,
merges_url: String,
archive_url: String,
downloads_url: String,
issues_url: String,
pulls_url: String,
milestones_url: String,
notifications_url: String,
releases_url: String,
deployments_url: String
)

final case class Repository(
id: Long,
name: String,
Expand Down
20 changes: 20 additions & 0 deletions github4s/shared/src/main/scala/github4s/domain/Search.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package github4s.domain

final case class SearchResultTextMatch(
object_url: String,
object_type: Option[String],
property: String,
fragment: String,
matches: List[SearchResultTextMatchLocation]
)

final case class SearchResultTextMatchLocation(
text: String,
indices: List[Int]
)

sealed abstract class ComparisonOperator(val value: String)
case object LesserThan extends ComparisonOperator("<=")
case object StrictlyLesserThan extends ComparisonOperator("<")
case object GreaterThan extends ComparisonOperator(">=")
case object StrictlyGreaterThan extends ComparisonOperator(">")
88 changes: 88 additions & 0 deletions github4s/shared/src/main/scala/github4s/domain/SearchCode.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package github4s.domain

final case class SearchCodeResult(
total_count: Int,
incomplete_results: Boolean,
items: List[SearchCodeResultItem]
)

final case class SearchCodeResultItem(
name: String,
path: String,
sha: String,
url: String,
git_url: String,
html_url: String,
repository: RepositoryMinimal,
score: Double,
file_size: Option[Long],
language: Option[String],
last_modified_at: Option[String],
line_numbers: Option[List[String]],
text_matches: Option[List[SearchResultTextMatch]]
)

sealed trait SearchCodeParam {
protected def paramName: String
protected def paramValue: String
def value: String = s"$paramName:$paramValue"
}

object SearchCodeParam {

final case class In(values: Set[In.Value]) extends SearchCodeParam {
override def paramName: String = "in"
override def paramValue: String = values.map(_.value).mkString(",")
}
object In {
sealed trait Value {
def value: String
}
final case object File extends Value {
override def value: String = "file"
}
final case object Path extends Value {
override def value: String = "path"
}
}

final case class User(name: String) extends SearchCodeParam {
override def paramName: String = "user"
override def paramValue: String = name
}

final case class Organization(name: String) extends SearchCodeParam {
override def paramName: String = "org"
override def paramValue: String = name
}

final case class Repository(user: String, repository: String) extends SearchCodeParam {
override def paramName: String = "repo"
override def paramValue: String = s"$user/$repository"
}

final case class Path(path: String) extends SearchCodeParam {
override def paramName: String = "path"
override def paramValue: String = path
}

final case class Language(language: String) extends SearchCodeParam {
override def paramName: String = "language"
override def paramValue: String = language
}

final case class Size(op: Option[ComparisonOperator] = None, size: Long) extends SearchCodeParam {
override def paramName: String = "size"
override def paramValue: String = s"${op.getOrElse("")}$size"
}

final case class Filename(filename: String) extends SearchCodeParam {
override def paramName: String = "filename"
override def paramValue: String = filename
}

final case class Extension(extension: String) extends SearchCodeParam {
override def paramName: String = "extension"
override def paramValue: String = extension
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2016-2022 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.interpreters

import github4s.Decoders._
import github4s.GHResponse
import github4s.algebras.Search
import github4s.domain._
import github4s.http.HttpClient

class SearchInterpreter[F[_]](implicit client: HttpClient[F]) extends Search[F] {

private val textMatchesHeader = "Accept" -> "application/vnd.github.text-match+json"

override def searchCode(
query: String,
searchParams: List[SearchCodeParam] = Nil,
textMatches: Boolean = false,
pagination: Option[Pagination] = None,
headers: Map[String, String] = Map.empty
): F[GHResponse[SearchCodeResult]] =
client.get[SearchCodeResult](
method = s"search/code",
if (textMatches) headers + textMatchesHeader else headers,
params = Map("q" -> s"$query+${searchParams.map(_.value).mkString("+")}"),
pagination
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class GithubAPIv3[F[_]: Concurrent](
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]

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ class EncoderDecoderSpec extends AnyFlatSpec with ScalaCheckPropertyChecks {
test[RepoPermissions]
test[RepositoryBase]
test[Repository]
test[RepositoryMinimal]
test[ReviewersRequest]
test[ReviewersResponse]
test[BranchUpdateRequest]
Expand All @@ -172,5 +173,8 @@ class EncoderDecoderSpec extends AnyFlatSpec with ScalaCheckPropertyChecks {
test[WriteFileRequest]
test[WriteFileResponse]
test[WriteResponseCommit]

test[SearchResultTextMatch]
test[SearchResultTextMatchLocation]
test[SearchCodeResult]
test[SearchCodeResultItem]
}
Loading

0 comments on commit cd361a1

Please sign in to comment.