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

Add search code endpoint #182

Merged
merged 5 commits into from
Feb 6, 2024
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
162 changes: 162 additions & 0 deletions OctoKit/Search.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
//
// Search.swift
//
//
// Created by Chidi Williams on 04/02/2024.
//

import Foundation
import RequestKit
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

open class SearchResponse<T: Codable>: Codable {
open var totalCount: Int
open var incompleteResults: Bool
open var items: [T]

init(totalCount: Int, incompleteResults: Bool, items: [T]) {
self.totalCount = totalCount
self.incompleteResults = incompleteResults
self.items = items
}

enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}

open class CodeSearchResultItem: Codable {
open var name: String
open var path: String
open var sha: String
open var url: URL
open var gitUrl: URL
open var htmlUrl: URL
open var repository: Repository
open var score: Double
open var fileSize: Int?
open var language: String?
open var lastModifiedAt: Date?
open var lineNumbers: [String]?

init(name: String,
path: String,
sha: String,
url: URL,
gitUrl: URL,
htmlUrl: URL,
repository: Repository,
score: Double,
fileSize: Int? = nil,
language: String? = nil,
lastModifiedAt: Date? = nil,
lineNumbers: [String]? = nil) {
self.name = name
self.path = path
self.sha = sha
self.url = url
self.gitUrl = gitUrl
self.htmlUrl = htmlUrl
self.repository = repository
self.score = score
self.fileSize = fileSize
self.language = language
self.lastModifiedAt = lastModifiedAt
self.lineNumbers = lineNumbers
}

enum CodingKeys: String, CodingKey {
case name, path, sha, url
case gitUrl = "git_url"
case htmlUrl = "html_url"
case repository, score
case fileSize = "file_size"
case language
case lastModifiedAt = "last_modified_at"
case lineNumbers = "line_numbers"
}
}

// MARK: request

public extension Octokit {
/**
Searches for query terms inside of a file
- parameter query: The query containing one or more search keywords and qualifiers.
- parameter page: Current page for label pagination. `1` by default.
- parameter perPage: Number of labels per page. `100` by default.
- parameter completion: Callback for the outcome of the fetch.
*/
@discardableResult
func searchCode(query: String,
page: String = "1",
perPage: String = "100",
completion: @escaping (_ response: Result<SearchResponse<CodeSearchResultItem>, Error>) -> Void) -> URLSessionDataTaskProtocol? {
let router = SearchRouter.searchCode(configuration, query, page, perPage)
return router.load(session, dateDecodingStrategy: .formatted(Time.rfc3339DateFormatter), expectedResultType: SearchResponse<CodeSearchResultItem>.self) { response, error in
if let error = error {
completion(.failure(error))
} else {
if let response = response {
completion(.success(response))
}
}
}
}

#if compiler(>=5.5.2) && canImport(_Concurrency)
/**
Searches for query terms inside of a file
- parameter query: The query containing one or more search keywords and qualifiers.
- parameter page: Current page for label pagination. `1` by default.
- parameter perPage: Number of labels per page. `100` by default.
*/
@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
func searchCode(query: String, page: String = "1", perPage: String = "100") async throws -> SearchResponse<CodeSearchResultItem> {
let router = SearchRouter.searchCode(configuration, query, page, perPage)
return try await router.load(session, dateDecodingStrategy: .formatted(Time.rfc3339DateFormatter), expectedResultType: SearchResponse<CodeSearchResultItem>.self)
}
#endif
}

enum SearchRouter: JSONPostRouter {
case searchCode(Configuration, String, String, String)

var method: HTTPMethod {
switch self {
case .searchCode:
return .GET
}
}

var encoding: HTTPEncoding {
switch self {
case .searchCode:
return .url
}
}

var configuration: Configuration {
switch self {
case let .searchCode(config, _, _, _): return config
}
}

var params: [String: Any] {
switch self {
case let .searchCode(_, query, page, perPage):
return ["q": query, "per_page": perPage, "page": page]
}
}

var path: String {
switch self {
case .searchCode:
return "search/code"
}
}
}
82 changes: 82 additions & 0 deletions Tests/OctoKitTests/Fixtures/search_code.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
{
"total_count": 1,
"incomplete_results": false,
"items": [
{
"name": "README",
"path": "README",
"sha": "980a0d5f19a64b4b30a87d4206aade58726b60e3",
"url": "https://api.github.com/repositories/1296269/contents/README?ref=7fd1a60b01f91b314f59955a4e4d4e80d8edf11d",
"git_url": "https://api.github.com/repositories/1296269/git/blobs/980a0d5f19a64b4b30a87d4206aade58726b60e3",
"html_url": "https://github.com/octocat/Hello-World/blob/7fd1a60b01f91b314f59955a4e4d4e80d8edf11d/README",
"repository": {
"id": 1296269,
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
"name": "Hello-World",
"full_name": "octocat/Hello-World",
"private": false,
"owner": {
"login": "octocat",
"id": 583231,
"node_id": "MDQ6VXNlcjU4MzIzMQ==",
"avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"html_url": "https://github.com/octocat/Hello-World",
"description": "My first repository on GitHub!",
"fork": false,
"url": "https://api.github.com/repos/octocat/Hello-World",
"forks_url": "https://api.github.com/repos/octocat/Hello-World/forks",
"keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}",
"collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}",
"teams_url": "https://api.github.com/repos/octocat/Hello-World/teams",
"hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks",
"issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}",
"events_url": "https://api.github.com/repos/octocat/Hello-World/events",
"assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}",
"branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}",
"tags_url": "https://api.github.com/repos/octocat/Hello-World/tags",
"blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}",
"git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}",
"git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}",
"trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}",
"statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}",
"languages_url": "https://api.github.com/repos/octocat/Hello-World/languages",
"stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers",
"contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors",
"subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers",
"subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription",
"commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}",
"git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}",
"comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}",
"issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}",
"contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}",
"compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}",
"merges_url": "https://api.github.com/repos/octocat/Hello-World/merges",
"archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}",
"downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads",
"issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}",
"pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}",
"milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}",
"notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}",
"labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}",
"releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}",
"deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments"
},
"score": 1.0
}
]
}
59 changes: 59 additions & 0 deletions Tests/OctoKitTests/SearchTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// SearchTests.swift
//
//
// Created by Chidi Williams on 04/02/2024.
//

import OctoKit
import XCTest

final class SearchTests: XCTestCase {
// MARK: Request Tests

func testSearchCode() {
let session = OctoKitURLTestSession(expectedURL: "https://api.github.com/search/code?page=1&per_page=100&q=hello%2Brepo%3Aoctocat/hello-world", expectedHTTPMethod: "GET",
jsonFile: "search_code", statusCode: 200)
let task = Octokit(session: session).searchCode(query: "hello+repo:octocat/hello-world") { response in
switch response {
case let .success(result):
XCTAssertEqual(result.totalCount, 1)
XCTAssertEqual(result.items.count, 1)
case let .failure(error):
print(error)
XCTFail("should not get an error")
}
}
XCTAssertNotNil(task)
XCTAssertTrue(session.wasCalled)
}

#if compiler(>=5.5.2) && canImport(_Concurrency)
@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
func testSearchCodeAsync() async throws {
let session = OctoKitURLTestSession(expectedURL: "https://api.github.com/search/code?page=1&per_page=100&q=hello%2Brepo%3Aoctocat/hello-world",
expectedHTTPMethod: "GET",
jsonFile: "search_code",
statusCode: 200)
let response = try await Octokit(session: session).searchCode(query: "hello+repo:octocat/hello-world")
XCTAssertEqual(response.totalCount, 1)
XCTAssertEqual(response.items.count, 1)
XCTAssertTrue(session.wasCalled)
}
#endif

func testSearchCodeSetsPagination() {
let session = OctoKitURLTestSession(expectedURL: "https://api.github.com/search/code?page=2&per_page=50&q=hello%2Brepo%3Aoctocat/hello-world", expectedHTTPMethod: "GET", jsonFile: nil,
statusCode: 200)
let task = Octokit(session: session).searchCode(query: "hello+repo:octocat/hello-world", page: "2", perPage: "50") { response in
switch response {
case .success:
XCTAssert(true)
case .failure:
XCTFail("should not get an error")
}
}
XCTAssertNotNil(task)
XCTAssertTrue(session.wasCalled)
}
}
Loading