Property definitions

github $ GithubAPI :: defaultinit
# Client to Github API
#
# To access the API you need an instance of a `GithubAPI` client.
#
# ~~~nitish
# # Get Github authentification token.
# var token = get_github_oauth
# assert not token.is_empty
#
# # Init the client.
# var api = new GithubAPI(token)
# ~~~
#
# The API client allows you to get Github API entities.
#
# ~~~nitish
# var repo = api.get_repo("nitlang/nit")
# assert repo != null
# assert repo.name == "nit"
#
# var user = api.get_user("Morriar")
# assert user != null
# assert user.login == "Morriar"
# ~~~
class GithubAPI

	# Github API OAuth token
	#
	# To access your private ressources, you must
	# [authenticate](https://developer.github.com/guides/basics-of-authentication/).
	#
	# For client applications, Github recommands to use the
	# [OAuth tokens](https://developer.github.com/v3/oauth/) authentification method.
	#
	#
	#
	# Be aware that there is [rate limits](https://developer.github.com/v3/rate_limit/)
	# associated to the key.
	var auth: nullable String = null is optional

	# User agent used for HTTP requests.
	#
	# Default is `nit_github_api`.
	#
	# See <https://developer.github.com/v3/#user-agent-required>
	var user_agent: String = "nit_github_api" is optional

	# Headers to use on all requests
	fun new_headers: HeaderMap do
		var map = new HeaderMap
		var auth = self.auth
		if auth != null then
			map["Authorization"] = "token {auth}"
		end
		map["User-Agent"] = user_agent
		# FIXME remove when projects and team are no more in beta
		map["Accept"] = "application/vnd.github.inertia-preview+json"
		map["Accept"] = "application/vnd.github.hellcat-preview+json"
		return map
	end

	# Github API base url.
	#
	# Default is `https://api.github.com` and should not be changed.
	var api_url = "https://api.github.com"

	# Send a HTTPRequest to the Github API
	fun send(method, path: String, headers: nullable HeaderMap, body: nullable String): nullable String do
		last_error = null
		path = sanitize_uri(path)
		var uri = "{api_url}{path}"
		var request = new CurlHTTPRequest(uri)
		request.method = method
		request.user_agent = user_agent
		request.headers = headers or else self.new_headers
		request.body = body
		return check_response(uri, request.execute)
	end

	private fun check_response(uri: String, response: CurlResponse): nullable String do
		if response isa CurlResponseSuccess then
			was_error = false
			return response.body_str
		else if response isa CurlResponseFailed then
			last_error = new GithubAPIError(
				response.error_msg,
				response.error_code,
				uri
			)
			was_error = true
			return null
		else abort
	end

	# Deserialize an object
	fun deserialize(string: nullable Serializable): nullable Object do
		if string == null then return null
		var deserializer = new GithubDeserializer(string.to_s)
		var res = deserializer.deserialize
		if deserializer.errors.not_empty then
			was_error = true
			last_error = new GithubDeserializerErrors("Deserialization failed", deserializer.errors)
			return null
		else if res isa GithubError then
			was_error = true
			last_error = res
			return null
		end
		was_error = false
		return res
	end

	# Escape `uri` in an acceptable format for Github.
	private fun sanitize_uri(uri: String): String do
		# TODO better URI escape.
		return uri.replace(" ", "%20")
	end

	# Last error occured during Github API communications.
	var last_error: nullable Error = null is public writable

	# Does the last request provoqued an error?
	var was_error = false is protected writable

	# Execute a GET request on Github API.
	#
	# This method returns a deserialized result.
	#
	# For raw data see `send`.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var obj = api.get("/repos/nitlang/nit")
	# assert obj isa Repo
	# assert obj.name == "nit"
	# ~~~
	#
	# Returns `null` in case of `error`.
	#
	# ~~~nitish
	# obj = api.get("/foo/bar/baz")
	# assert obj == null
	# assert api.was_error
	# assert api.last_error isa GithubError
	# ~~~
	fun get(path: String, headers: nullable HeaderMap, data: nullable String): nullable Object do
		return deserialize(send("GET", path, headers, data))
	end

	# Get the Github logged user from `auth` token.
	#
	# Loads the `User` from the API or returns `null` if the user cannot be found.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var user = api.get_auth_user
	# assert user.login == "Morriar"
	# ~~~
	fun get_auth_user: nullable User do
		return get("/user").as(nullable User)
	end

	# Get the Github user with `login`
	#
	# Loads the `User` from the API or returns `null` if the user cannot be found.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var user = api.get_user("Morriar")
	# print user or else "null"
	# assert user.login == "Morriar"
	# ~~~
	fun get_user(login: String): nullable User do
		return get("/users/{login}").as(nullable User)
	end

	# Get the Github repo with `full_name`.
	#
	# Loads the `Repo` from the API or returns `null` if the repo cannot be found.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var repo = api.get_repo("nitlang/nit")
	# assert repo.name == "nit"
	# assert repo.owner.login == "nitlang"
	# assert repo.default_branch == "master"
	# ~~~
	fun get_repo(repo_slug: String): nullable Repo do
		return get("/repos/{repo_slug}").as(nullable Repo)
	end

	# List of repo branches.
	#
	# Pagination:
	#	* `page`: page to fetch (default: 1)
	#	* `per_page`: number of branches by page (default: 30)
	fun get_repo_branches(repo_slug: String, page, per_page: nullable Int): Array[Branch] do
		return new GithubArray[Branch].from(get(
			"/repos/{repo_slug}/branches?{pagination(page, per_page)}"))
	end

	# List of issues associated with their ids.
	fun get_repo_issues(repo_slug: String, page, per_page: nullable Int): Array[Issue] do
		return new GithubArray[Issue].from(get(
			"/repos/{repo_slug}/issues?{pagination(page, per_page)}"))
	end

	# Search issues in this repo form an advanced query.
	#
	# Example:
	#
	# ~~~nitish
	# var issues = repo.search_issues("is:open label:need_review")
	# ~~~
	#
	# See <https://developer.github.com/v3/search/#search-issues>.
	fun search_repo_issues(repo_slug: String, query: String, page, per_page: nullable Int): nullable SearchResults do
		return get("/search/issues?q={query} repo:{repo_slug}&{pagination(page, per_page)}").as(nullable SearchResults)
	end

	# List of labels associated with their names.
	fun get_repo_labels(repo_slug: String, page, per_page: nullable Int): Array[Label] do
		return new GithubArray[Label].from(get(
			"/repos/{repo_slug}/labels?{pagination(page, per_page)}"))
	end

	# List of milestones associated with their ids.
	fun get_repo_milestones(repo_slug: String, page, per_page: nullable Int): Array[Milestone] do
		return new GithubArray[Milestone].from(get(
			"/repos/{repo_slug}/milestones?{pagination(page, per_page)}"))
	end

	# List of pull-requests associated with their ids.
	#
	# Implementation notes: because PR numbers are not consecutive,
	# PR are loaded from pages.
	# See: https://developer.github.com/v3/pulls/#list-pull-requests
	fun get_repo_pulls(repo_slug: String, page, per_page: nullable Int): Array[PullRequest] do
		return new GithubArray[PullRequest].from(get(
			"/repos/{repo_slug}/pulls?{pagination(page, per_page)}"))
	end

	# List of contributor related statistics.
	fun get_repo_contrib_stats(repo_slug: String): Array[ContributorStats] do
		return new GithubArray[ContributorStats].from(get("/repos/{repo_slug}/stats/contributors"))
	end

	# Get the Github branch with `name`.
	#
	# Returns `null` if the branch cannot be found.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var repo = api.get_repo("nitlang/nit")
	# assert repo != null
	# var branch = api.get_branch(repo, "master")
	# assert branch.name == "master"
	# assert branch.commit isa Commit
	# ~~~
	fun get_branch(repo_slug: String, name: String): nullable Branch do
		return get("/repos/{repo_slug}/branches/{name}").as(nullable Branch)
	end

	# Get the Github commit with `sha`.
	#
	# Returns `null` if the commit cannot be found.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var repo = api.get_repo("nitlang/nit")
	# assert repo != null
	# var commit = api.get_commit(repo, "64ce1f")
	# assert commit isa Commit
	# ~~~
	fun get_commit(repo_slug: String, sha: String): nullable Commit do
		return get("/repos/{repo_slug}/commits/{sha}").as(nullable Commit)
	end

	# Get the status of a commit
	#
	# The status holds the result of each check ran on a commit like CI, reviews etc.
	fun get_commit_status(repo_slug: String, sha: String): nullable CommitStatus do
		return get("/repos/{repo_slug}/commits/{sha}/status").as(nullable CommitStatus)
	end

	# Get the Github issue #`number`.
	#
	# Returns `null` if the issue cannot be found.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var repo = api.get_repo("nitlang/nit")
	# assert repo != null
	# var issue = api.get_issue(repo, 1)
	# assert issue.title == "Doc"
	# ~~~
	fun get_issue(repo_slug: String, number: Int): nullable Issue do
		return get("/repos/{repo_slug}/issues/{number}").as(nullable Issue)
	end

	# List of event on this issue.
	fun get_issue_comments(repo_slug: String, issue_number: Int, page, per_page: nullable Int): Array[IssueComment] do
		return new GithubArray[IssueComment].from(get(
			"/repos/{repo_slug}/issues/{issue_number}/comments?{pagination(page, per_page)}"))
	end

	# List of events on this issue.
	fun get_issue_events(repo_slug: String, issue_number: Int, page, per_page: nullable Int): Array[IssueEvent] do
		return new GithubArray[IssueEvent].from(get(
			"/repos/{repo_slug}/issues/{issue_number}/events?{pagination(page, per_page)}"))
	end

	# Get the Github pull request #`number`.
	#
	# Returns `null` if the pull request cannot be found.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var repo = api.get_repo("nitlang/nit")
	# assert repo != null
	# var pull = api.get_pull(repo, 1)
	# assert pull.title == "Doc"
	# assert pull.user.login == "Morriar"
	# ~~~
	fun get_pull(repo_slug: String, number: Int): nullable PullRequest do
		return get("/repos/{repo_slug}/pulls/{number}").as(nullable PullRequest)
	end

	# List of comments on a pull request
	fun get_pull_comments(repo_slug: String, pull_number: Int, page, per_page: nullable Int): Array[PullComment] do
		return new GithubArray[PullComment].from(get(
			"/repos/{repo_slug}/pulls/{pull_number}/comments?{pagination(page, per_page)}"))
	end

	# Get a specific pull request comment
	fun get_pull_comment(repo_slug: String, id: Int): nullable PullComment do
		return get("/repos/{repo_slug}/pulls/comments/{id}").as(nullable PullComment)
	end

	# Get the Github label with `name`.
	#
	# Returns `null` if the label cannot be found.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var repo = api.get_repo("nitlang/nit")
	# assert repo != null
	# var labl = api.get_label(repo, "ok_will_merge")
	# assert labl != null
	# ~~~
	fun get_label(repo_slug: String, name: String): nullable Label do
		return get("/repos/{repo_slug}/labels/{name}").as(nullable Label)
	end

	# Get the Github milestone with `id`.
	#
	# Returns `null` if the milestone cannot be found.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var repo = api.get_repo("nitlang/nit")
	# assert repo != null
	# var stone = api.get_milestone(repo, 4)
	# assert stone.title == "v1.0prealpha"
	# ~~~
	fun get_milestone(repo_slug: String, id: Int): nullable Milestone do
		return get("/repos/{repo_slug}/milestones/{id}").as(nullable Milestone)
	end

	# Get the Github issue event with `id`.
	#
	# Returns `null` if the event cannot be found.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var repo = api.get_repo("nitlang/nit")
	# assert repo isa Repo
	# var event = api.get_issue_event(repo, 199674194)
	# assert event isa IssueEvent
	# assert event.actor.login == "privat"
	# assert event.event == "labeled"
	# assert event.labl isa Label
	# assert event.labl.name == "need_review"
	# ~~~
	fun get_issue_event(repo_slug: String, id: Int): nullable IssueEvent do
		return get("/repos/{repo_slug}/issues/events/{id}").as(nullable IssueEvent)
	end

	# Get the Github commit comment with `id`.
	#
	# Returns `null` if the comment cannot be found.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var repo = api.get_repo("nitlang/nit")
	# assert repo != null
	# var comment = api.get_commit_comment(repo, 8982707)
	# assert comment.user.login == "Morriar"
	# assert comment.body == "For testing purposes...\n"
	# assert comment.commit_id == "7eacb86d1e24b7e72bc9ac869bf7182c0300ceca"
	# ~~~
	fun get_commit_comment(repo_slug: String, id: Int): nullable CommitComment do
		return get("/repos/{repo_slug}/comments/{id}").as(nullable CommitComment)
	end

	# Get the Github issue comment with `id`.
	#
	# Returns `null` if the comment cannot be found.
	#
	# ~~~nitish
	# var api = new GithubAPI(get_github_oauth)
	# var repo = api.get_repo("nitlang/nit")
	# assert repo != null
	# var comment = api.get_issue_comment(repo, 6020149)
	# assert comment.user.login == "privat"
	# assert comment.created_at.to_s == "2012-05-30T20:16:54Z"
	# assert comment.issue_number == 10
	# ~~~
	fun get_issue_comment(repo_slug: String, id: Int): nullable IssueComment do
		return get("/repos/{repo_slug}/issues/comments/{id}").as(nullable IssueComment)
	end

	private fun pagination(page, per_page: nullable Int): String do
		return "page={page or else 1}&per_page={per_page or else 30}"
	end
end
lib/github/api.nit:28,1--453,3