Thin GitHub API client for MoonBit built on gaato/http and gaato/mttp: the whole REST API generated, plus a GraphQL passthrough.
Status: unofficial and experimental. Minor releases may contain breaking changes; the changelog lists each one with a migration note.
moon add gaato/github
moon add gaato/http
moon add gaato/mttp
moon add gaato/http-async///|
let backend = @codings.Decompress::new(@backend.AsyncBackend::new())
///|
let clock = @runtime.AsyncClock::new()///|
/// Reads a repository's star count.
pub async fn stars(
backend : &@http.Backend,
clock : &@rt.Clock,
token~ : String,
) -> Result[Int32, @github.GitHubFailure] {
let gh = @github.GitHub::new(backend, clock, token~)
let repo = gh.call(
@gen.repos_get_request("gaato", "mbt-sdk"),
@gen.repos_get_decode,
)
match repo {
Ok(repo) => Ok(repo.stargazers_count)
Err(error) => Err(error)
}
}| Package | What it is |
|---|---|
| gaato/github | The GitHub client: sending, pagination, GraphQL, rate limits, retries and error helpers |
| gaato/github/gen | Every REST operation and its types, generated from the OpenAPI description |
///|
/// Reads the diff between two refs.
pub async fn compare_diff(
gh : @github.GitHub,
range : String,
) -> Result[String, @github.GitHubFailure] {
let response = gh.send(
@gen.repos_compare_commits_request("gaato", "mbt-sdk", range).header(
"accept", "application/vnd.github.diff",
),
)
match response {
Ok(response) => Ok(@utf8.decode_lossy(response.body))
Err(error) => Err(error)
}
}///|
/// Lists up to 250 of a user's repositories.
pub async fn user_repos(
gh : @github.GitHub,
user : String,
) -> Result[Array[@gen.MinimalRepository], @github.GitHubFailure] {
let repos = gh.paginator(
@gen.repos_list_for_user_request(user, per_page=100),
@gen.repos_list_for_user_decode,
)
repos.collect(max=250)
}///|
/// Reads a repository's star count through GraphQL.
pub async fn stars_graphql(
gh : @github.GitHub,
) -> Result[Json, @github.GitHubFailure] {
gh.graphql(
query="query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { stargazerCount } }",
variables={ "owner": "gaato", "name": "mbt-sdk" },
)
}///|
let gh = @github.GitHub::new(backend, clock, token~, gate=() => {
@runtime.SemaphoreGate::new()
})test {
let body : @github.ApiErrorBody = {
message: "Not Found",
documentation_url: Some("https://docs.github.com/rest"),
status: Some("404"),
errors: None,
}
assert_eq(body.message, "Not Found")
}pub struct GitHub {
// private fields
}async fn[T] GitHub::call(self : GitHub, request : Request, decode : (ResponseHead, Bytes) -> Result[T, ResponseFailure] raise?) -> Result[T, GitHubFailure] let repo = gh.call(
@gen.repos_get_request("gaato", "mbt-sdk"),
@gen.repos_get_decode,
) let data = gh.graphql(
query=(
#|query($owner: String!, $name: String!) {
#| repository(owner: $owner, name: $name) { stargazerCount }
#|}
),
variables={ "owner": "gaato", "name": "mbt-sdk" },
) let issues = gh
.graphql_paginator(
data => {
guard data is Object(root) else { ... }
...
(nodes, if has_next_page { end_cursor } else { None })
},
query=(
#|query($cursor: String) {
#| repository(owner: "gaato", name: "mbt-sdk") {
#| issues(first: 100, after: $cursor) {
#| nodes { number title }
#| pageInfo { hasNextPage endCursor }
#| }
#| }
#|}
),
cursor_variable="cursor",
)
.collect(max=500)fn GitHub::new(backend : &Backend, clock : &Clock, token? : String, base_url? : String, api_version? : String, user_agent? : String, retry? : RetryPolicy, limiter? : &RateLimiter, graphql_url? : String, gate? : () -> &Gate, timer? : &Timer, timeout_ms? : Int, max_body_bytes? : Int, read_idle_ms? : Int, total_timeout_ms? : Int) -> GitHubfn[T] GitHub::paginator(self : GitHub, first : Request, decode : (ResponseHead, Bytes) -> Result[Array[T], ResponseFailure] raise?) -> Paginator[T, GitHubFailure]pub(all) enum GitHubFailure {
Http(ResponseFailure)
GraphqlFailure(ResponseHead, message~ : String, body~ : Bytes)
Validation(String)
} derive(Debug)fn GitHubRetry::decide(self : GitHubRetry, error : GitHubFailure, context : RetryContext) -> Decisiontest {
let retry : @github.GitHubRetry = {
policy: @client.RetryPolicy::conservative().with_max_retries(3),
}
let post = @http.Request::post("/repos/o/r/issues")
let limited = @github.Http(
@client.UnexpectedStatus(
{ status: 403, headers: @http.Headers::empty(), },
body=b"{\"message\":\"You have exceeded a secondary rate limit.\"}",
),
)
let waits = [0, 1, 2, 3].map(attempt => {
retry.decide(limited, @client.RetryContext::capture(post, attempt~))
})
assert_eq(waits, [Wait(60000), Wait(120000), Wait(240000), Stop(Exhausted)])
}test {
let error : @github.GraphqlError = {
message: "Could not resolve to a Repository with the name 'o/r'.",
type_: Some("NOT_FOUND"),
path: Some(["repository"]),
locations: None,
extensions: None,
}
assert_eq(error.type_, Some("NOT_FOUND"))
} if @github.graphql_errors(error) is Some(errors) {
for error in errors {
if error.type_ is Some("RATE_LIMITED") { ... }
}
}Install
Download zipThin GitHub API client for MoonBit built on gaato/http and gaato/mttp: the whole REST API generated, plus a GraphQL passthrough.