use crate::client::GitHubClient;
use miyabi_types::error::{MiyabiError, Result};
use miyabi_types::issue::{Issue, IssueState, IssueStateGithub};
use octocrab::models::issues::Issue as OctoIssue;
use octocrab::params::State;
impl GitHubClient {
pub async fn get_issue(&self, number: u64) -> Result<Issue> {
let issue = self
.client
.issues(&self.owner, &self.repo)
.get(number)
.await
.map_err(|e| {
MiyabiError::GitHub(format!(
"Failed to get issue #{} from {}/{}: {}",
number, self.owner, self.repo, e
))
})?;
convert_issue(issue)
}
pub async fn list_issues(
&self,
state: Option<State>,
labels: Vec<String>,
) -> Result<Vec<Issue>> {
let issues = self.client.issues(&self.owner, &self.repo);
let mut handler = issues.list();
if let Some(s) = state {
handler = handler.state(s);
}
if !labels.is_empty() {
handler = handler.labels(&labels);
}
let page = handler.send().await.map_err(|e| {
MiyabiError::GitHub(format!(
"Failed to list issues for {}/{}: {}",
self.owner, self.repo, e
))
})?;
page.items.into_iter().map(convert_issue).collect()
}
pub async fn create_issue(&self, title: &str, body: Option<&str>) -> Result<Issue> {
let issues = self.client.issues(&self.owner, &self.repo);
let mut handler = issues.create(title);
if let Some(b) = body {
handler = handler.body(b);
}
let issue = handler.send().await.map_err(|e| {
MiyabiError::GitHub(format!(
"Failed to create issue in {}/{}: {}",
self.owner, self.repo, e
))
})?;
convert_issue(issue)
}
pub async fn update_issue(
&self,
number: u64,
title: Option<&str>,
body: Option<&str>,
state: Option<State>,
) -> Result<Issue> {
use octocrab::models::IssueState as OctoState;
let issues = self.client.issues(&self.owner, &self.repo);
let mut handler = issues.update(number);
if let Some(t) = title {
handler = handler.title(t);
}
if let Some(b) = body {
handler = handler.body(b);
}
if let Some(s) = state {
let issue_state = match s {
State::Open => OctoState::Open,
State::Closed => OctoState::Closed,
State::All => {
return Err(MiyabiError::GitHub(
"Cannot update issue to 'All' state".to_string(),
))
}
_ => return Err(MiyabiError::GitHub(format!("Unknown state: {:?}", s))),
};
handler = handler.state(issue_state);
}
let issue = handler.send().await.map_err(|e| {
MiyabiError::GitHub(format!(
"Failed to update issue #{} in {}/{}: {}",
number, self.owner, self.repo, e
))
})?;
convert_issue(issue)
}
pub async fn close_issue(&self, number: u64) -> Result<Issue> {
self.update_issue(number, None, None, Some(State::Closed))
.await
}
pub async fn reopen_issue(&self, number: u64) -> Result<Issue> {
self.update_issue(number, None, None, Some(State::Open))
.await
}
pub async fn add_labels(&self, number: u64, labels: &[String]) -> Result<Vec<String>> {
let labels_result = self
.client
.issues(&self.owner, &self.repo)
.add_labels(number, labels)
.await
.map_err(|e| {
MiyabiError::GitHub(format!(
"Failed to add labels to issue #{} in {}/{}: {}",
number, self.owner, self.repo, e
))
})?;
Ok(labels_result.into_iter().map(|l| l.name).collect())
}
pub async fn remove_label(&self, number: u64, label: &str) -> Result<()> {
self.client
.issues(&self.owner, &self.repo)
.remove_label(number, label)
.await
.map_err(|e| {
MiyabiError::GitHub(format!(
"Failed to remove label '{}' from issue #{} in {}/{}: {}",
label, number, self.owner, self.repo, e
))
})?;
Ok(())
}
pub async fn replace_labels(&self, number: u64, labels: &[String]) -> Result<Vec<String>> {
let labels_result = self
.client
.issues(&self.owner, &self.repo)
.replace_all_labels(number, labels)
.await
.map_err(|e| {
MiyabiError::GitHub(format!(
"Failed to replace labels on issue #{} in {}/{}: {}",
number, self.owner, self.repo, e
))
})?;
Ok(labels_result.into_iter().map(|l| l.name).collect())
}
pub async fn get_issues_by_state(&self, state: IssueState) -> Result<Vec<Issue>> {
let label = state.to_label().to_string();
self.list_issues(Some(State::Open), vec![label]).await
}
}
fn convert_issue(issue: OctoIssue) -> Result<Issue> {
use octocrab::models::IssueState as OctoState;
let state = match issue.state {
OctoState::Open => IssueStateGithub::Open,
OctoState::Closed => IssueStateGithub::Closed,
_ => {
return Err(MiyabiError::GitHub(format!(
"Unknown issue state: {:?}",
issue.state
)))
}
};
let assignee = issue.assignee.map(|a| a.login);
let labels = issue
.labels
.into_iter()
.map(|l| l.name)
.collect::<Vec<String>>();
Ok(Issue {
number: issue.number,
title: issue.title,
body: issue.body.unwrap_or_default(),
state,
labels,
assignee,
created_at: issue.created_at,
updated_at: issue.updated_at,
url: issue.html_url.to_string(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_issue_state_conversion() {
assert_eq!(IssueState::Pending.to_label(), "📥 state:pending");
assert_eq!(IssueState::Analyzing.to_label(), "🔍 state:analyzing");
assert_eq!(IssueState::Implementing.to_label(), "🏗️ state:implementing");
assert_eq!(IssueState::Reviewing.to_label(), "👀 state:reviewing");
assert_eq!(IssueState::Deploying.to_label(), "🚀 state:deploying");
assert_eq!(IssueState::Done.to_label(), "✅ state:done");
assert_eq!(IssueState::Blocked.to_label(), "🚫 state:blocked");
assert_eq!(IssueState::Failed.to_label(), "❌ state:failed");
}
}