bitbucket_cli/api/
issues.rs

1use anyhow::Result;
2
3use super::BitbucketClient;
4use crate::models::{
5    CreateIssueCommentRequest, CreateIssueRequest, Issue, IssueComment, IssueState, Paginated,
6};
7
8impl BitbucketClient {
9    /// List issues for a repository
10    pub async fn list_issues(
11        &self,
12        workspace: &str,
13        repo_slug: &str,
14        state: Option<IssueState>,
15        page: Option<u32>,
16        pagelen: Option<u32>,
17    ) -> Result<Paginated<Issue>> {
18        let mut query = Vec::new();
19
20        if let Some(s) = state {
21            query.push(("state", s.to_string()));
22        }
23        if let Some(p) = page {
24            query.push(("page", p.to_string()));
25        }
26        if let Some(len) = pagelen {
27            query.push(("pagelen", len.to_string()));
28        }
29
30        let query_refs: Vec<(&str, &str)> = query.iter().map(|(k, v)| (*k, v.as_str())).collect();
31
32        let path = format!("/repositories/{}/{}/issues", workspace, repo_slug);
33        self.get_with_query(&path, &query_refs).await
34    }
35
36    /// Get a specific issue
37    pub async fn get_issue(
38        &self,
39        workspace: &str,
40        repo_slug: &str,
41        issue_id: u64,
42    ) -> Result<Issue> {
43        let path = format!(
44            "/repositories/{}/{}/issues/{}",
45            workspace, repo_slug, issue_id
46        );
47        self.get(&path).await
48    }
49
50    /// Create a new issue
51    pub async fn create_issue(
52        &self,
53        workspace: &str,
54        repo_slug: &str,
55        request: &CreateIssueRequest,
56    ) -> Result<Issue> {
57        let path = format!("/repositories/{}/{}/issues", workspace, repo_slug);
58        self.post(&path, request).await
59    }
60
61    /// Update an issue
62    pub async fn update_issue(
63        &self,
64        workspace: &str,
65        repo_slug: &str,
66        issue_id: u64,
67        title: Option<&str>,
68        content: Option<&str>,
69        state: Option<IssueState>,
70    ) -> Result<Issue> {
71        #[derive(serde::Serialize)]
72        struct UpdateRequest {
73            #[serde(skip_serializing_if = "Option::is_none")]
74            title: Option<String>,
75            #[serde(skip_serializing_if = "Option::is_none")]
76            content: Option<ContentRequest>,
77            #[serde(skip_serializing_if = "Option::is_none")]
78            state: Option<IssueState>,
79        }
80
81        #[derive(serde::Serialize)]
82        struct ContentRequest {
83            raw: String,
84        }
85
86        let request = UpdateRequest {
87            title: title.map(|t| t.to_string()),
88            content: content.map(|c| ContentRequest { raw: c.to_string() }),
89            state,
90        };
91
92        let path = format!(
93            "/repositories/{}/{}/issues/{}",
94            workspace, repo_slug, issue_id
95        );
96        self.put(&path, &request).await
97    }
98
99    /// Delete an issue
100    pub async fn delete_issue(
101        &self,
102        workspace: &str,
103        repo_slug: &str,
104        issue_id: u64,
105    ) -> Result<()> {
106        let path = format!(
107            "/repositories/{}/{}/issues/{}",
108            workspace, repo_slug, issue_id
109        );
110        self.delete(&path).await
111    }
112
113    /// List comments on an issue
114    pub async fn list_issue_comments(
115        &self,
116        workspace: &str,
117        repo_slug: &str,
118        issue_id: u64,
119    ) -> Result<Paginated<IssueComment>> {
120        let path = format!(
121            "/repositories/{}/{}/issues/{}/comments",
122            workspace, repo_slug, issue_id
123        );
124        self.get(&path).await
125    }
126
127    /// Add a comment to an issue
128    pub async fn add_issue_comment(
129        &self,
130        workspace: &str,
131        repo_slug: &str,
132        issue_id: u64,
133        content: &str,
134    ) -> Result<IssueComment> {
135        let request = CreateIssueCommentRequest {
136            content: crate::models::IssueContentRequest {
137                raw: content.to_string(),
138            },
139        };
140
141        let path = format!(
142            "/repositories/{}/{}/issues/{}/comments",
143            workspace, repo_slug, issue_id
144        );
145        self.post(&path, &request).await
146    }
147
148    /// Vote for an issue
149    pub async fn vote_issue(&self, workspace: &str, repo_slug: &str, issue_id: u64) -> Result<()> {
150        let path = format!(
151            "/repositories/{}/{}/issues/{}/vote",
152            workspace, repo_slug, issue_id
153        );
154        self.put::<serde_json::Value, _>(&path, &serde_json::json!({}))
155            .await?;
156        Ok(())
157    }
158
159    /// Remove vote from an issue
160    pub async fn unvote_issue(
161        &self,
162        workspace: &str,
163        repo_slug: &str,
164        issue_id: u64,
165    ) -> Result<()> {
166        let path = format!(
167            "/repositories/{}/{}/issues/{}/vote",
168            workspace, repo_slug, issue_id
169        );
170        self.delete(&path).await
171    }
172
173    /// Watch an issue
174    pub async fn watch_issue(&self, workspace: &str, repo_slug: &str, issue_id: u64) -> Result<()> {
175        let path = format!(
176            "/repositories/{}/{}/issues/{}/watch",
177            workspace, repo_slug, issue_id
178        );
179        self.put::<serde_json::Value, _>(&path, &serde_json::json!({}))
180            .await?;
181        Ok(())
182    }
183
184    /// Unwatch an issue
185    pub async fn unwatch_issue(
186        &self,
187        workspace: &str,
188        repo_slug: &str,
189        issue_id: u64,
190    ) -> Result<()> {
191        let path = format!(
192            "/repositories/{}/{}/issues/{}/watch",
193            workspace, repo_slug, issue_id
194        );
195        self.delete(&path).await
196    }
197}