use serde::{Deserialize, Serialize};
use super::github::{CreatedIssue, ExistingIssue, GithubApi, GithubFilingError};
const GITHUB_API: &str = "https://api.github.com";
const REPO: &str = "bobmatnyc/trusty-tools";
const API_VERSION: &str = "2022-11-28";
const USER_AGENT: &str = concat!("trusty-mpm/", env!("CARGO_PKG_VERSION"));
#[derive(Debug, Deserialize)]
struct SearchItem {
html_url: String,
number: u64,
}
#[derive(Debug, Deserialize)]
struct SearchResponse {
items: Vec<SearchItem>,
}
#[derive(Debug, Deserialize)]
struct IssueResponse {
html_url: String,
number: u64,
}
#[derive(Debug, Serialize)]
struct CreateIssueBody<'a> {
title: &'a str,
body: &'a str,
labels: &'a [String],
}
#[derive(Debug, Serialize)]
struct CreateCommentBody<'a> {
body: &'a str,
}
pub struct RealGithubClient {
token: String,
}
impl RealGithubClient {
pub fn new(token: String) -> Self {
Self { token }
}
fn http_client(&self) -> Result<reqwest::blocking::Client, GithubFilingError> {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::ACCEPT,
"application/vnd.github+json".parse().map_err(
|e: reqwest::header::InvalidHeaderValue| {
GithubFilingError::Transport(e.to_string())
},
)?,
);
headers.insert(
reqwest::header::AUTHORIZATION,
format!("Bearer {}", self.token).parse().map_err(
|e: reqwest::header::InvalidHeaderValue| {
GithubFilingError::Transport(e.to_string())
},
)?,
);
headers.insert(
"X-GitHub-Api-Version",
API_VERSION
.parse()
.map_err(|e: reqwest::header::InvalidHeaderValue| {
GithubFilingError::Transport(e.to_string())
})?,
);
reqwest::blocking::Client::builder()
.user_agent(USER_AGENT)
.default_headers(headers)
.build()
.map_err(|e| GithubFilingError::Transport(e.to_string()))
}
}
impl GithubApi for RealGithubClient {
fn search_open_issues(
&self,
fingerprint: &str,
) -> Result<Vec<ExistingIssue>, GithubFilingError> {
let client = self.http_client()?;
let query =
format!(r#"repo:{REPO} is:issue is:open "trusty-bug-fingerprint: {fingerprint}""#);
let url = format!("{GITHUB_API}/search/issues");
let resp = client
.get(&url)
.query(&[("q", &query)])
.send()
.map_err(|e| GithubFilingError::Transport(e.to_string()))?;
let status = resp.status().as_u16();
if !resp.status().is_success() {
let body = resp.text().unwrap_or_default();
return Err(GithubFilingError::ApiError { status, body });
}
let search: SearchResponse = resp
.json()
.map_err(|e| GithubFilingError::Parse(e.to_string()))?;
Ok(search
.items
.into_iter()
.map(|item| ExistingIssue {
html_url: item.html_url,
number: item.number,
})
.collect())
}
fn create_issue(
&self,
title: &str,
body: &str,
labels: &[String],
) -> Result<CreatedIssue, GithubFilingError> {
let client = self.http_client()?;
let url = format!("{GITHUB_API}/repos/{REPO}/issues");
let payload = CreateIssueBody {
title,
body,
labels,
};
let resp = client
.post(&url)
.json(&payload)
.send()
.map_err(|e| GithubFilingError::Transport(e.to_string()))?;
let status = resp.status().as_u16();
if !resp.status().is_success() {
let body = resp.text().unwrap_or_default();
return Err(GithubFilingError::ApiError { status, body });
}
let issue: IssueResponse = resp
.json()
.map_err(|e| GithubFilingError::Parse(e.to_string()))?;
Ok(CreatedIssue {
html_url: issue.html_url,
number: issue.number,
})
}
fn add_comment(&self, issue_number: u64, body: &str) -> Result<(), GithubFilingError> {
let client = self.http_client()?;
let url = format!("{GITHUB_API}/repos/{REPO}/issues/{issue_number}/comments");
let payload = CreateCommentBody { body };
let resp = client
.post(&url)
.json(&payload)
.send()
.map_err(|e| GithubFilingError::Transport(e.to_string()))?;
let status = resp.status().as_u16();
if !resp.status().is_success() {
let body = resp.text().unwrap_or_default();
return Err(GithubFilingError::ApiError { status, body });
}
Ok(())
}
}