Skip to content

Commit b2aa183

Browse files
Merge pull request #818 from nicoulaj/issue-751-search-code
#751: add support for code search API
2 parents dbad86d + 7a8390a commit b2aa183

16 files changed

Lines changed: 496 additions & 2 deletions

File tree

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ lazy val github4s = (crossProject(JSPlatform, JVMPlatform))
2222
.settings(coreDeps: _*)
2323
.settings(
2424
// Increase number of inlines, needed for circe semiauto derivation
25-
scalacOptions ++= on(3, 9)(Seq("-Xmax-inlines", "20")).value.flatten,
25+
scalacOptions ++= on(3)(Seq("-Xmax-inlines", "48")).value.flatten,
2626
// See the README for why this is necessary
2727
// https://github.com/scala-js/scala-js-macrotask-executor/tree/v1.0.0
2828
// tl;dr: without it, performance problems and concurrency bugs abound

github4s/shared/src/main/scala/github4s/Decoders.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,9 @@ object Decoders {
200200
source <- Decoder[Option[RepositoryBase]].at("source")
201201
} yield Repository.fromBaseRepos(base, parent, source)
202202

203+
implicit val decodeRepositoryMinimal: Decoder[RepositoryMinimal] =
204+
deriveDecoder[RepositoryMinimal]
205+
203206
implicit val decodePRStatus: Decoder[PullRequestReviewState] =
204207
Decoder.decodeString.emap {
205208
case PRRStateApproved.value => PRRStateApproved.asRight
@@ -412,4 +415,13 @@ object Decoders {
412415
deriveDecoder[BranchUpdateResponse]
413416
implicit val decodeCommitComparisonResponse: Decoder[CommitComparisonResponse] =
414417
deriveDecoder[CommitComparisonResponse]
418+
419+
implicit val decodeSearchResultTextMatch: Decoder[SearchResultTextMatch] =
420+
deriveDecoder[SearchResultTextMatch]
421+
implicit val decodeSearchResultTextMatchLocation: Decoder[SearchResultTextMatchLocation] =
422+
deriveDecoder[SearchResultTextMatchLocation]
423+
implicit val decodeSearchCodeResult: Decoder[SearchCodeResult] =
424+
deriveDecoder[SearchCodeResult]
425+
implicit val decodeSearchCodeResultItem: Decoder[SearchCodeResultItem] =
426+
deriveDecoder[SearchCodeResultItem]
415427
}

github4s/shared/src/main/scala/github4s/Encoders.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ object Encoders {
238238
case None => repo
239239
}
240240
}
241+
implicit val encodeRepositoryMinimal: Encoder[RepositoryMinimal] =
242+
deriveEncoder[RepositoryMinimal]
241243

242244
implicit val encoderPublicGitHubEvents: Encoder[PublicGitHubEvent] =
243245
Encoder.instance { e =>
@@ -282,4 +284,13 @@ object Encoders {
282284
}
283285
implicit val encodeCommitComparisonResponse: Encoder[CommitComparisonResponse] =
284286
deriveEncoder[CommitComparisonResponse]
287+
288+
implicit val encodeSearchResultTextMatch: Encoder[SearchResultTextMatch] =
289+
deriveEncoder[SearchResultTextMatch]
290+
implicit val encodeSearchResultTextMatchLocation: Encoder[SearchResultTextMatchLocation] =
291+
deriveEncoder[SearchResultTextMatchLocation]
292+
implicit val encodeSearchCodeResult: Encoder[SearchCodeResult] =
293+
deriveEncoder[SearchCodeResult]
294+
implicit val encodeSearchCodeResultItem: Encoder[SearchCodeResultItem] =
295+
deriveEncoder[SearchCodeResultItem]
285296
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class Github[F[_]: Concurrent](
4141
lazy val organizations: Organizations[F] = module.organizations
4242
lazy val teams: Teams[F] = module.teams
4343
lazy val projects: Projects[F] = module.projects
44+
lazy val search: Search[F] = module.search
4445
}
4546

4647
object Github {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,9 @@ trait GithubAPIs[F[_]] {
7474
* Project-related operations.
7575
*/
7676
def projects: Projects[F]
77+
78+
/**
79+
* Search-related operations.
80+
*/
81+
def search: Search[F]
7782
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2016-2022 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.algebras
18+
19+
import github4s.GHResponse
20+
import github4s.domain._
21+
22+
trait Search[F[_]] {
23+
24+
/**
25+
* Search code
26+
*
27+
* @param query query string
28+
* @param searchParams search parameters
29+
* @param textMatches enable text matches
30+
* @param pagination Limit and Offset for pagination
31+
* @param headers optional user headers to include in the request
32+
* @return GHResponse[SearchCodeResult] the search results
33+
*/
34+
def searchCode(
35+
query: String,
36+
searchParams: List[SearchCodeParam],
37+
textMatches: Boolean = false,
38+
pagination: Option[Pagination] = None,
39+
headers: Map[String, String] = Map()
40+
): F[GHResponse[SearchCodeResult]]
41+
42+
}

github4s/shared/src/main/scala/github4s/domain/Repository.scala

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,54 @@ final case class RepositoryBase(
3939
topics: List[String] = Nil
4040
)
4141

42+
/** A `Repository` but with some information omitted (this is used by the search API) */
43+
final case class RepositoryMinimal(
44+
id: Long,
45+
node_id: String,
46+
name: String,
47+
full_name: String,
48+
`private`: Boolean,
49+
owner: User,
50+
html_url: String,
51+
description: Option[String] = None,
52+
fork: Boolean,
53+
url: String,
54+
forks_url: String,
55+
keys_url: String,
56+
collaborators_url: String,
57+
teams_url: String,
58+
hooks_url: String,
59+
issue_events_url: String,
60+
events_url: String,
61+
assignees_url: String,
62+
branches_url: String,
63+
blobs_url: String,
64+
git_tags_url: String,
65+
git_refs_url: String,
66+
trees_url: String,
67+
statuses_url: String,
68+
languages_url: String,
69+
stargazers_url: String,
70+
contributors_url: String,
71+
subscribers_url: String,
72+
subscription_url: String,
73+
commits_url: String,
74+
git_commits_url: String,
75+
comments_url: String,
76+
issue_comment_url: String,
77+
contents_url: String,
78+
compare_url: String,
79+
merges_url: String,
80+
archive_url: String,
81+
downloads_url: String,
82+
issues_url: String,
83+
pulls_url: String,
84+
milestones_url: String,
85+
notifications_url: String,
86+
releases_url: String,
87+
deployments_url: String
88+
)
89+
4290
final case class Repository(
4391
id: Long,
4492
name: String,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package github4s.domain
2+
3+
final case class SearchResultTextMatch(
4+
object_url: String,
5+
object_type: Option[String],
6+
property: String,
7+
fragment: String,
8+
matches: List[SearchResultTextMatchLocation]
9+
)
10+
11+
final case class SearchResultTextMatchLocation(
12+
text: String,
13+
indices: List[Int]
14+
)
15+
16+
sealed abstract class ComparisonOperator(val value: String)
17+
case object LesserThan extends ComparisonOperator("<=")
18+
case object StrictlyLesserThan extends ComparisonOperator("<")
19+
case object GreaterThan extends ComparisonOperator(">=")
20+
case object StrictlyGreaterThan extends ComparisonOperator(">")
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package github4s.domain
2+
3+
final case class SearchCodeResult(
4+
total_count: Int,
5+
incomplete_results: Boolean,
6+
items: List[SearchCodeResultItem]
7+
)
8+
9+
final case class SearchCodeResultItem(
10+
name: String,
11+
path: String,
12+
sha: String,
13+
url: String,
14+
git_url: String,
15+
html_url: String,
16+
repository: RepositoryMinimal,
17+
score: Double,
18+
file_size: Option[Long],
19+
language: Option[String],
20+
last_modified_at: Option[String],
21+
line_numbers: Option[List[String]],
22+
text_matches: Option[List[SearchResultTextMatch]]
23+
)
24+
25+
sealed trait SearchCodeParam {
26+
protected def paramName: String
27+
protected def paramValue: String
28+
def value: String = s"$paramName:$paramValue"
29+
}
30+
31+
object SearchCodeParam {
32+
33+
final case class In(values: Set[In.Value]) extends SearchCodeParam {
34+
override def paramName: String = "in"
35+
override def paramValue: String = values.map(_.value).mkString(",")
36+
}
37+
object In {
38+
sealed trait Value {
39+
def value: String
40+
}
41+
case object File extends Value {
42+
override def value: String = "file"
43+
}
44+
case object Path extends Value {
45+
override def value: String = "path"
46+
}
47+
}
48+
49+
final case class User(name: String) extends SearchCodeParam {
50+
override def paramName: String = "user"
51+
override def paramValue: String = name
52+
}
53+
54+
final case class Organization(name: String) extends SearchCodeParam {
55+
override def paramName: String = "org"
56+
override def paramValue: String = name
57+
}
58+
59+
final case class Repository(owner: String, repo: String) extends SearchCodeParam {
60+
override def paramName: String = "repo"
61+
override def paramValue: String = s"$owner/$repo"
62+
}
63+
64+
final case class Path(path: String) extends SearchCodeParam {
65+
override def paramName: String = "path"
66+
override def paramValue: String = path
67+
}
68+
69+
final case class Language(language: String) extends SearchCodeParam {
70+
override def paramName: String = "language"
71+
override def paramValue: String = language
72+
}
73+
74+
final case class Size(op: Option[ComparisonOperator] = None, size: Long) extends SearchCodeParam {
75+
override def paramName: String = "size"
76+
override def paramValue: String = s"${op.getOrElse("")}$size"
77+
}
78+
79+
final case class Filename(filename: String) extends SearchCodeParam {
80+
override def paramName: String = "filename"
81+
override def paramValue: String = filename
82+
}
83+
84+
final case class Extension(extension: String) extends SearchCodeParam {
85+
override def paramName: String = "extension"
86+
override def paramValue: String = extension
87+
}
88+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2016-2022 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.interpreters
18+
19+
import github4s.Decoders._
20+
import github4s.GHResponse
21+
import github4s.algebras.Search
22+
import github4s.domain._
23+
import github4s.http.HttpClient
24+
25+
class SearchInterpreter[F[_]](implicit client: HttpClient[F]) extends Search[F] {
26+
27+
private val textMatchesHeader = "Accept" -> "application/vnd.github.text-match+json"
28+
29+
override def searchCode(
30+
query: String,
31+
searchParams: List[SearchCodeParam] = Nil,
32+
textMatches: Boolean = false,
33+
pagination: Option[Pagination] = None,
34+
headers: Map[String, String] = Map.empty
35+
): F[GHResponse[SearchCodeResult]] =
36+
client.get[SearchCodeResult](
37+
method = s"search/code",
38+
if (textMatches) headers + textMatchesHeader else headers,
39+
params = Map("q" -> s"$query+${searchParams.map(_.value).mkString("+")}"),
40+
pagination
41+
)
42+
}

0 commit comments

Comments
 (0)