Skip to content

Commit

Permalink
Implement Repository contents API (#189)
Browse files Browse the repository at this point in the history
* Add API implementation for repository contents

* Add unit tests for Repository Contents
  • Loading branch information
Nikoloutsos committed Aug 2, 2024
1 parent a8290f7 commit c0f67f9
Show file tree
Hide file tree
Showing 5 changed files with 766 additions and 0 deletions.
183 changes: 183 additions & 0 deletions OctoKit/Repositories.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,135 @@ open class Repository: Codable {
}
}

public struct Links: Codable {
public let git: String?
public let html: String?
public let selfLink: String

private enum CodingKeys: String, CodingKey {
case git, html
case selfLink = "self"
}
}

public struct ContentDirectoryItem: Codable {
public let type: String
public let size: Int
public let name: String
public let path: String
public let content: String?
public let sha: String
public let url: String
public let gitUrl: String?
public let htmlUrl: String?
public let downloadUrl: String?
public let links: Links

private enum CodingKeys: String, CodingKey {
case type, size, name, path, content, sha, url
case gitUrl = "git_url"
case htmlUrl = "html_url"
case downloadUrl = "download_url"
case links = "_links"
}
}

public struct ContentFile: Codable {
public let type: String
public let encoding: String
public let size: Int
public let name: String
public let path: String
public let content: String
public let sha: String
public let url: String
public let gitUrl: String?
public let htmlUrl: String?
public let downloadUrl: String?
public let links: Links

private enum CodingKeys: String, CodingKey {
case type, encoding, size, name, path, content, sha, url
case gitUrl = "git_url"
case htmlUrl = "html_url"
case downloadUrl = "download_url"
case links = "_links"
}
}

public struct SymlinkContent: Codable {
public let type: String
public let target: String
public let size: Int
public let name: String
public let path: String
public let sha: String
public let url: String
public let gitUrl: String?
public let htmlUrl: String?
public let downloadUrl: String?
public let links: Links

private enum CodingKeys: String, CodingKey {
case type, target, size, name, path, sha, url
case gitUrl = "git_url"
case htmlUrl = "html_url"
case downloadUrl = "download_url"
case links = "_links"
}
}

public struct SubmoduleContent: Codable {
public let type: String
public let submoduleGitUrl: String
public let size: Int
public let name: String
public let path: String
public let sha: String
public let url: String
public let gitUrl: String?
public let htmlUrl: String?
public let downloadUrl: String?
public let links: Links

private enum CodingKeys: String, CodingKey {
case type, submoduleGitUrl = "submodule_git_url", size, name, path, sha, url
case gitUrl = "git_url"
case htmlUrl = "html_url"
case downloadUrl = "download_url"
case links = "_links"
}
}

/// Response for decoding https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28 response
public enum ContentResponse: Codable {
case contentDirectory([ContentDirectoryItem])
case contentFile(ContentFile)
case symlinkContent(SymlinkContent)
case submoduleContent(SubmoduleContent)

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let contentDirectoryItem = try? container.decode([ContentDirectoryItem].self) {
self = .contentDirectory(contentDirectoryItem)
return
}
if let contentFile = try? container.decode(ContentFile.self) {
self = .contentFile(contentFile)
return
}
if let symlinkContent = try? container.decode(SymlinkContent.self) {
self = .symlinkContent(symlinkContent)
return
}
if let submoduleContent = try? container.decode(SubmoduleContent.self) {
self = .submoduleContent(submoduleContent)
return
}
throw DecodingError.typeMismatch(ContentResponse.self, DecodingError.Context(codingPath: container.codingPath, debugDescription: "Unexpected type"))
}
}

// MARK: request

public extension Octokit {
Expand Down Expand Up @@ -149,6 +278,49 @@ public extension Octokit {
return try await router.load(session, dateDecodingStrategy: .formatted(Time.rfc3339DateFormatter), expectedResultType: Repository.self)
}
#endif

/**
Gets the contents of a file or directory in a repository.
[Github documentation](https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28)
- parameter owner: The account owner of the repository. The name is not case sensitive.
- parameter name: The name of the repository without the .git extension. The name is not case sensitive.
- parameter path: Specify the file path or directory with the path parameter. If you omit the path parameter, you will receive the contents of the repository's root directory.
- parameter ref: The name of the commit/branch/tag. Default: the repository’s default branch.
- parameter completion: Callback for the outcome of the fetch. Depending on the provided path a different enum value may be returned.
*/
@discardableResult
func repositoryContent(owner: String,
name: String,
path: String?,
ref: String?,
completion: @escaping (_ response: Result<ContentResponse, Error>) -> Void) -> URLSessionDataTaskProtocol? {
let router = RepositoryRouter.getRepositoryContent(configuration, owner, name, path, ref)
return router.load(session, dateDecodingStrategy: .formatted(Time.rfc3339DateFormatter), expectedResultType: ContentResponse.self) { contentResponse, error in
if let error = error {
completion(.failure(error))
} else {
if let contentResponse = contentResponse {
completion(.success(contentResponse))
}
}
}
}

#if compiler(>=5.5.2) && canImport(_Concurrency)
/**
Gets the contents of a file or directory in a repository.
[Github documentation](https://docs.github.com/en/rest/repos/contents?apiVersion=2022-11-28)
- parameter owner: The account owner of the repository. The name is not case sensitive.
- parameter name: The name of the repository without the .git extension. The name is not case sensitive.
- parameter path: Specify the file path or directory with the path parameter. If you omit the path parameter, you will receive the contents of the repository's root directory.
- parameter ref: The name of the commit/branch/tag. Default: the repository’s default branch.
*/
@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
func repositoryContent(owner: String, name: String, path: String?, ref: String? = nil) async throws -> ContentResponse {
let router = RepositoryRouter.getRepositoryContent(configuration, owner, name, path, ref)
return try await router.load(session, dateDecodingStrategy: .formatted(Time.rfc3339DateFormatter), expectedResultType: ContentResponse.self)
}
#endif
}

// MARK: Router
Expand All @@ -157,12 +329,14 @@ enum RepositoryRouter: Router {
case readRepositories(Configuration, String, String, String)
case readAuthenticatedRepositories(Configuration, String, String)
case readRepository(Configuration, String, String)
case getRepositoryContent(Configuration, String, String, String?, String?)

var configuration: Configuration {
switch self {
case let .readRepositories(config, _, _, _): return config
case let .readAuthenticatedRepositories(config, _, _): return config
case let .readRepository(config, _, _): return config
case let .getRepositoryContent(config, _, _, _, _): return config
}
}

Expand All @@ -182,6 +356,11 @@ enum RepositoryRouter: Router {
return ["per_page": perPage, "page": page]
case .readRepository:
return [:]
case let .getRepositoryContent(_, _, _, _, ref):
if let ref = ref {
return ["ref": ref]
}
return [:]
}
}

Expand All @@ -193,6 +372,10 @@ enum RepositoryRouter: Router {
return "user/repos"
case let .readRepository(_, owner, name):
return "repos/\(owner)/\(name)"
case let .getRepositoryContent(_, owner, repo, searchPath, _):
var path = "repos/\(owner)/\(repo)/contents"
if let searchPath = searchPath { path.append("/\(searchPath)") }
return path
}
}
}
18 changes: 18 additions & 0 deletions Tests/OctoKitTests/Fixtures/content.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"_links": {
"git": "https://api.github.com/repos/nerdishbynature/octokit.swift/git/blobs/7ff4c2ce2bce119effa766ba6845200f59e17414",
"html": "https://github.com/nerdishbynature/octokit.swift/blob/main/Package.swift",
"self": "https://api.github.com/repos/nerdishbynature/octokit.swift/contents/Package.swift?ref=main"
},
"content": "Ly8gc3dpZnQtdG9vbHMtdmVyc2lvbjo0LjAKLy8gVGhlIHN3aWZ0LXRvb2xz\nLXZlcnNpb24gZGVjbGFyZXMgdGhlIG1pbmltdW0gdmVyc2lvbiBvZiBTd2lm\ndCByZXF1aXJlZCB0byBidWlsZCB0aGlzIHBhY2thZ2UuCgppbXBvcnQgUGFj\na2FnZURlc2NyaXB0aW9uCgpsZXQgcGFja2FnZSA9IFBhY2thZ2UoCiAgICBu\nYW1lOiAiT2N0b0tpdCIsCgogICAgcHJvZHVjdHM6IFsKICAgICAgICAubGli\ncmFyeSgKICAgICAgICAgICAgbmFtZTogIk9jdG9LaXQiLAogICAgICAgICAg\nICB0YXJnZXRzOiBbIk9jdG9LaXQiXQogICAgICAgICksCiAgICBdLAogICAg\nZGVwZW5kZW5jaWVzOiBbCiAgICAgICAgLnBhY2thZ2UodXJsOiAiaHR0cHM6\nLy9naXRodWIuY29tL25lcmRpc2hieW5hdHVyZS9SZXF1ZXN0S2l0LmdpdCIs\nIGZyb206ICIzLjMuMCIpLAogICAgICAgIC5wYWNrYWdlKHVybDogImh0dHBz\nOi8vZ2l0aHViLmNvbS9uaWNrbG9ja3dvb2QvU3dpZnRGb3JtYXQiLCBmcm9t\nOiAiMC41Mi44IikKICAgIF0sCiAgICB0YXJnZXRzOiBbCiAgICAgICAgLnRh\ncmdldCgKICAgICAgICAgICAgbmFtZTogIk9jdG9LaXQiLAogICAgICAgICAg\nICBkZXBlbmRlbmNpZXM6IFsiUmVxdWVzdEtpdCJdLAogICAgICAgICAgICBw\nYXRoOiAiT2N0b0tpdCIKICAgICAgICApLAogICAgICAgIC50ZXN0VGFyZ2V0\nKAogICAgICAgICAgICBuYW1lOiAiT2N0b0tpdFRlc3RzIiwKICAgICAgICAg\nICAgZGVwZW5kZW5jaWVzOiBbIk9jdG9LaXQiXQogICAgICAgICksCiAgICBd\nCikK\n",
"download_url": "https://raw.githubusercontent.com/nerdishbynature/octokit.swift/main/Package.swift",
"encoding": "base64",
"git_url": "https://api.github.com/repos/nerdishbynature/octokit.swift/git/blobs/7ff4c2ce2bce119effa766ba6845200f59e17414",
"html_url": "https://github.com/nerdishbynature/octokit.swift/blob/main/Package.swift",
"name": "Package.swift",
"path": "Package.swift",
"sha": "7ff4c2ce2bce119effa766ba6845200f59e17414",
"size": 768,
"type": "file",
"url": "https://api.github.com/repos/nerdishbynature/octokit.swift/contents/Package.swift?ref=main"
}
17 changes: 17 additions & 0 deletions Tests/OctoKitTests/Fixtures/content_submodule.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"_links": {
"git": "https://api.github.com/repos/muter-mutation-testing/homebrew-formulae/git/trees/4e30ce4a9b6137502cf131664d412000bdd93007",
"html": "https://github.com/muter-mutation-testing/homebrew-formulae/tree/4e30ce4a9b6137502cf131664d412000bdd93007",
"self": "https://api.github.com/repos/muter-mutation-testing/muter/contents/homebrew-formulae?ref=master"
},
"download_url": null,
"git_url": "https://api.github.com/repos/muter-mutation-testing/homebrew-formulae/git/trees/4e30ce4a9b6137502cf131664d412000bdd93007",
"html_url": "https://github.com/muter-mutation-testing/homebrew-formulae/tree/4e30ce4a9b6137502cf131664d412000bdd93007",
"name": "homebrew-formulae",
"path": "homebrew-formulae",
"sha": "4e30ce4a9b6137502cf131664d412000bdd93007",
"size": 0,
"submodule_git_url": "https://github.com/muter-mutation-testing/homebrew-formulae.git",
"type": "submodule",
"url": "https://api.github.com/repos/muter-mutation-testing/muter/contents/homebrew-formulae?ref=master"
}
Loading

0 comments on commit c0f67f9

Please sign in to comment.