-
Notifications
You must be signed in to change notification settings - Fork 129
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add search code * Fix query param key * Fix search query key * Fix linting
- Loading branch information
1 parent
30f8645
commit 9d238d3
Showing
3 changed files
with
303 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |