use std::path::Path;
use anyhow::Context;
use async_trait::async_trait;
use super::client::{CodeForgeClient, PullRequest};
use super::remote::GitHubRepo;
pub struct OctocrabGitHubClient {
client: octocrab::Octocrab,
gh_repo: GitHubRepo,
}
impl std::fmt::Debug for OctocrabGitHubClient {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OctocrabGitHubClient")
.field("gh_repo", &self.gh_repo)
.finish_non_exhaustive()
}
}
impl OctocrabGitHubClient {
pub fn new(client: octocrab::Octocrab, gh_repo: GitHubRepo) -> Self {
Self { client, gh_repo }
}
}
#[async_trait]
impl CodeForgeClient for OctocrabGitHubClient {
async fn create_release(
&self,
tag_name: &str,
name: &str,
body: &str,
) -> anyhow::Result<String> {
log::trace!("create_release: tag={tag_name} name={name}");
let release = self
.client
.repos(&self.gh_repo.owner, &self.gh_repo.repo)
.releases()
.create(tag_name)
.name(name)
.body(body)
.draft(true)
.prerelease(false)
.send()
.await
.with_context(|| format!("Failed to create GitHub Release for tag '{tag_name}'"))?;
Ok(release.id.to_string())
}
async fn upload_asset(
&self,
release_id: &str,
file_name: &str,
file_path: &Path,
) -> anyhow::Result<()> {
let id: u64 = release_id
.parse()
.with_context(|| format!("Invalid GitHub release_id: {release_id:?}"))?;
log::trace!("upload_asset: release_id={release_id} file={file_name}");
let data = tokio::fs::read(file_path)
.await
.with_context(|| format!("Failed to read asset file '{}'", file_path.display()))?;
self.client
.repos(&self.gh_repo.owner, &self.gh_repo.repo)
.releases()
.upload_asset(id, file_name, data.into())
.send()
.await
.with_context(|| format!("Failed to upload asset '{file_name}'"))?;
Ok(())
}
async fn create_pull_request(
&self,
title: &str,
body: &str,
head: &str,
base: &str,
) -> anyhow::Result<String> {
log::trace!("create_pull_request: title={title} head={head} base={base}");
let pr = self
.client
.pulls(&self.gh_repo.owner, &self.gh_repo.repo)
.create(title, head, base)
.body(body)
.send()
.await
.with_context(|| format!("Failed to create pull request '{title}'"))?;
let url = pr
.html_url
.context("GitHub API response missing html_url for created pull request")?;
Ok(url.to_string())
}
async fn find_open_pull_request(&self, head: &str) -> anyhow::Result<Option<PullRequest>> {
let head_filter = format!("{}:{}", self.gh_repo.owner, head);
log::trace!("find_open_pull_request: head={head_filter}");
let page = self
.client
.pulls(&self.gh_repo.owner, &self.gh_repo.repo)
.list()
.state(octocrab::params::State::Open)
.head(&head_filter)
.send()
.await
.with_context(|| format!("Failed to list pull requests for branch '{head}'"))?;
Ok(page.items.into_iter().next().map(|pr| {
let html_url = pr.html_url.map_or_else(String::new, |u| u.to_string());
PullRequest {
number: pr.number,
html_url,
}
}))
}
async fn update_pull_request(
&self,
pull_number: u64,
title: &str,
body: &str,
) -> anyhow::Result<String> {
log::trace!("update_pull_request: #{pull_number} title={title}");
let pr = self
.client
.pulls(&self.gh_repo.owner, &self.gh_repo.repo)
.update(pull_number)
.title(title)
.body(body)
.send()
.await
.with_context(|| format!("Failed to update pull request #{pull_number}"))?;
let url = pr
.html_url
.context("GitHub API response missing html_url for updated pull request")?;
Ok(url.to_string())
}
async fn publish_release(&self, release_id: &str) -> anyhow::Result<()> {
let id: u64 = release_id
.parse()
.with_context(|| format!("Invalid GitHub release_id: {release_id:?}"))?;
log::trace!("publish_release: release_id={release_id}");
self.client
.repos(&self.gh_repo.owner, &self.gh_repo.repo)
.releases()
.update(id)
.draft(false)
.send()
.await
.with_context(|| format!("Failed to publish GitHub Release {release_id}"))?;
Ok(())
}
}