summaryrefslogtreecommitdiffstats
path: root/src/github.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/github.rs')
-rw-r--r--src/github.rs86
1 files changed, 85 insertions, 1 deletions
diff --git a/src/github.rs b/src/github.rs
index 9a62994..55bfc19 100644
--- a/src/github.rs
+++ b/src/github.rs
@@ -1,7 +1,8 @@
-use failure::{Error, ResultExt};
+use failure::{bail, Error, ResultExt};
use reqwest::header::{self, HeaderValue};
use reqwest::{Client, Method, RequestBuilder};
use std::borrow::Cow;
+use std::collections::HashMap;
static API_BASE: &str = "https://api.github.com/";
static TOKEN_VAR: &str = "GITHUB_TOKEN";
@@ -13,6 +14,23 @@ pub(crate) struct User {
pub(crate) email: Option<String>,
}
+#[derive(serde::Deserialize)]
+struct GraphResult<T> {
+ data: Option<T>,
+ #[serde(default)]
+ errors: Vec<GraphError>,
+}
+
+#[derive(serde::Deserialize)]
+struct GraphError {
+ message: String,
+}
+
+#[derive(serde::Deserialize)]
+struct GraphNodes<T> {
+ nodes: Vec<Option<T>>,
+}
+
pub(crate) struct GitHubApi {
http: Client,
token: String,
@@ -40,6 +58,31 @@ impl GitHubApi {
))
}
+ fn graphql<R, V>(&self, query: &str, variables: V) -> Result<R, Error>
+ where
+ R: for<'de> serde::Deserialize<'de>,
+ V: serde::Serialize,
+ {
+ #[derive(serde::Serialize)]
+ struct Request<'a, V> {
+ query: &'a str,
+ variables: V,
+ }
+ let res: GraphResult<R> = self
+ .prepare(Method::POST, "graphql")?
+ .json(&Request { query, variables })
+ .send()?
+ .error_for_status()?
+ .json()?;
+ if let Some(error) = res.errors.iter().next() {
+ bail!("graphql error: {}", error.message);
+ } else if let Some(data) = res.data {
+ Ok(data)
+ } else {
+ bail!("missing graphql data");
+ }
+ }
+
pub(crate) fn user(&self, login: &str) -> Result<User, Error> {
Ok(self
.prepare(Method::GET, &format!("users/{}", login))?
@@ -47,4 +90,45 @@ impl GitHubApi {
.error_for_status()?
.json()?)
}
+
+ pub(crate) fn usernames(&self, ids: &[usize]) -> Result<HashMap<usize, String>, Error> {
+ #[derive(serde::Deserialize)]
+ #[serde(rename_all = "camelCase")]
+ struct Usernames {
+ database_id: usize,
+ login: String,
+ }
+ #[derive(serde::Serialize)]
+ struct Params {
+ ids: Vec<String>,
+ }
+ static QUERY: &str = "
+ query($ids: [ID!]!) {
+ nodes(ids: $ids) {
+ ... on User {
+ databaseId
+ login
+ }
+ }
+ }
+ ";
+
+ let mut result = HashMap::new();
+ for chunk in ids.chunks(100) {
+ let res: GraphNodes<Usernames> = self.graphql(
+ QUERY,
+ Params {
+ ids: chunk.iter().map(|id| user_node_id(*id)).collect(),
+ },
+ )?;
+ for node in res.nodes.into_iter().filter_map(|n| n) {
+ result.insert(node.database_id, node.login);
+ }
+ }
+ Ok(result)
+ }
+}
+
+fn user_node_id(id: usize) -> String {
+ base64::encode(&format!("04:User{}", id))
}