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

Implement Repository contents API #189

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
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
Loading