# For most use-cases you need to use the `GithubAPI` client.
module api
-import github_curl
+# TODO to remove
intrude import json::serialization_read
+import json::static
+
+import base64
+import curl
+import json
# Client to Github API
#
# See <https://developer.github.com/v3/#user-agent-required>
var user_agent: String = "nit_github_api" is optional
- # Curl instance.
- #
- # Internal Curl instance used to perform API calls.
- private var ghcurl = new GithubCurl(auth or else "", user_agent) is lazy
+ # 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.
#
# * `1`: verbose
var verbose_lvl = 0 is public writable
+ # 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: String): nullable Object do
- var deserializer = new GithubDeserializer(string)
+ 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
- # print deserializer.errors.join("\n") # DEBUG
+ 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
# assert err.name == "GithubAPIError"
# assert err.message == "Not Found"
# ~~~
- fun get(path: String): nullable Serializable do
- path = sanitize_uri(path)
- var res = ghcurl.get_and_parse("{api_url}{path}")
- if res isa Error then
- last_error = res
- was_error = true
- return null
- end
- was_error = false
- return res
+ fun get(path: String): nullable String do
+ return send("GET", path)
end
# Display a message depending on `verbose_lvl`.
protected fun load_from_github(key: String): nullable GithubEntity do
message(1, "Get {key} (github)")
var res = get(key)
- if was_error then return null
- return deserialize(res.as(JsonObject).to_json).as(nullable GithubEntity)
+ if res == null then return null
+ return deserialize(res).as(nullable GithubEntity)
end
# Get the Github logged user from `auth` token.
message(1, "Get branches for {repo.full_name}")
var array = get("/repos/{repo.full_name}/branches")
var res = new Array[Branch]
- if not array isa JsonArray then return res
- var deser = deserialize(array.to_json)
+ if array == null then return res
+ var deser = deserialize(array)
if not deser isa Array[Object] then return res # empty array
for branch in deser do
if not branch isa Branch then continue
end
end
+# An Error returned by GithubAPI
+class GithubError
+ super Error
+end
+
+# An Error returned by https://api.github.com
+#
+# Anything that can occurs when sending request to the API:
+# * Can't connect to API
+# * Ressource not found
+# * Validation error
+# * ...
+class GithubAPIError
+ super GithubError
+
+ # Status code obtained
+ var status_code: Int
+
+ # URI that returned the error
+ var requested_uri: String
+end
+
+# An Error returned while deserializing GithubEntity objects
+class GithubDeserializerErrors
+ super GithubError
+
+ # Errors returned by the deserizalization process
+ var deserizalization_errors: Array[Error]
+end
+
# Something returned by the Github API.
#
# Mainly a Nit wrapper around a JSON objet.
return super
end
end
+
+# Gets the Github token from `git` configuration
+#
+# Return the value of `git config --get github.oauthtoken`
+# or `""` if no key exists.
+fun get_github_oauth: String
+do
+ var p = new ProcessReader("git", "config", "--get", "github.oauthtoken")
+ var token = p.read_line
+ p.wait
+ p.close
+ return token.trim
+end
#
# Cache files can be automatically created and updated by setting
# `update_responses_cache` to `true` then running `nitunit`.
-class MockGithubCurl
- super GithubCurl
+class MockGithubAPI
+ super GithubAPI
# Mock so it returns the response from a file
#
# See `update_responses_cache`.
- redef fun get_and_parse(uri) do
- print uri # for debugging
+ redef fun send(method, path, headers, body) do
+ print path # for debugging
- var path = uri.replace("https://api.github.com/", "/")
assert has_response(path)
if update_responses_cache then
var file = response_file(path)
- save_actual_response(uri, file)
+ save_actual_response(path, file)
end
- var response = response_string(path).parse_json
+ var response = response_string(path)
if response_is_error(path) then
- var title = "GithubAPIError"
- var msg = response.as(JsonObject)["message"].as(String)
- var err = new GithubError(msg, title)
- err.json["requested_uri"] = uri
- err.json["status_code"] = response_code(path)
- return err
+ last_error = new GithubAPIError(
+ response.parse_json.as(JsonObject)["message"].as(String),
+ response_code(path).to_i,
+ path
+ )
+ was_error = true
+ return null
end
return response
end
private fun save_actual_response(uri, file: String) do
assert update_responses_cache
- var request = new CurlHTTPRequest(uri)
- request.user_agent = actual_curl.user_agent
- request.headers = actual_curl.header
+ var request = new CurlHTTPRequest("{api_url}{sanitize_uri(uri)}")
+ request.user_agent = actual_api.user_agent
+ request.headers = actual_api.new_headers
var response = request.execute
if response isa CurlResponseSuccess then
end
# Actual GithubCurl instance used for caching
- private var actual_curl = new GithubCurl(get_github_oauth, "nitunit")
+ private var actual_api = new GithubAPI(get_github_oauth, "nitunit")
end
class TestGithubAPI
test
- var mock = new MockGithubCurl("test", "test")
-
- fun api: GithubAPI do
- var api = new GithubAPI("test")
- api.ghcurl = mock
- return api
- end
+ fun api: MockGithubAPI do return new MockGithubAPI("test", "test")
fun test_deserialize is test do
- var response = mock.response_string("/users/Morriar")
+ var response = api.response_string("/users/Morriar")
var obj = api.deserialize(response)
assert obj isa User
assert obj.login == "Morriar"
var obj = api.get("/users/Morriar")
assert not api.was_error
assert api.last_error == null
- assert obj isa JsonObject
- assert obj["login"] == "Morriar"
+ assert obj != null
+ assert obj.parse_json.as(JsonObject)["login"] == "Morriar"
end
fun test_get_404 is test do
assert res == null
assert api.was_error
var err = api.last_error
- assert err isa GithubError
- assert err.name == "GithubAPIError"
+ assert err isa GithubAPIError
+ assert err.status_code == 404
assert err.message == "Not Found"
end
assert res == null
assert api.was_error
var err = api.last_error
- assert err isa GithubError
- assert err.name == "GithubAPIError"
+ assert err isa GithubAPIError
+ assert err.status_code == 404
assert err.message == "Not Found"
end