Skip to main content

bitbucket_cli/api/
pullrequests.rs

1use anyhow::Result;
2
3use super::BitbucketClient;
4use crate::models::{
5    CreatePullRequestRequest, MergePullRequestRequest, Paginated, PullRequest, PullRequestComment,
6    PullRequestState,
7};
8
9impl BitbucketClient {
10    /// List pull requests for a repository
11    pub async fn list_pull_requests(
12        &self,
13        workspace: &str,
14        repo_slug: &str,
15        state: Option<PullRequestState>,
16        page: Option<u32>,
17        pagelen: Option<u32>,
18    ) -> Result<Paginated<PullRequest>> {
19        let mut query = Vec::new();
20
21        if let Some(s) = state {
22            query.push(("state", s.to_string()));
23        }
24        if let Some(p) = page {
25            query.push(("page", p.to_string()));
26        }
27        if let Some(len) = pagelen {
28            query.push(("pagelen", len.to_string()));
29        }
30
31        let query_refs: Vec<(&str, &str)> = query.iter().map(|(k, v)| (*k, v.as_str())).collect();
32
33        let path = format!("/repositories/{}/{}/pullrequests", workspace, repo_slug);
34        self.get_with_query(&path, &query_refs).await
35    }
36
37    /// Get a specific pull request
38    pub async fn get_pull_request(
39        &self,
40        workspace: &str,
41        repo_slug: &str,
42        pr_id: u64,
43    ) -> Result<PullRequest> {
44        let path = format!(
45            "/repositories/{}/{}/pullrequests/{}",
46            workspace, repo_slug, pr_id
47        );
48        self.get(&path).await
49    }
50
51    /// Create a new pull request
52    pub async fn create_pull_request(
53        &self,
54        workspace: &str,
55        repo_slug: &str,
56        request: &CreatePullRequestRequest,
57    ) -> Result<PullRequest> {
58        let path = format!("/repositories/{}/{}/pullrequests", workspace, repo_slug);
59        self.post(&path, request).await
60    }
61
62    /// Update a pull request
63    pub async fn update_pull_request(
64        &self,
65        workspace: &str,
66        repo_slug: &str,
67        pr_id: u64,
68        title: Option<&str>,
69        description: Option<&str>,
70    ) -> Result<PullRequest> {
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            description: Option<String>,
77        }
78
79        let request = UpdateRequest {
80            title: title.map(|t| t.to_string()),
81            description: description.map(|d| d.to_string()),
82        };
83
84        let path = format!(
85            "/repositories/{}/{}/pullrequests/{}",
86            workspace, repo_slug, pr_id
87        );
88        self.put(&path, &request).await
89    }
90
91    /// Merge a pull request
92    pub async fn merge_pull_request(
93        &self,
94        workspace: &str,
95        repo_slug: &str,
96        pr_id: u64,
97        request: Option<&MergePullRequestRequest>,
98    ) -> Result<PullRequest> {
99        let default_request = MergePullRequestRequest::default();
100        let request = request.unwrap_or(&default_request);
101
102        let path = format!(
103            "/repositories/{}/{}/pullrequests/{}/merge",
104            workspace, repo_slug, pr_id
105        );
106        self.post(&path, request).await
107    }
108
109    /// Approve a pull request
110    pub async fn approve_pull_request(
111        &self,
112        workspace: &str,
113        repo_slug: &str,
114        pr_id: u64,
115    ) -> Result<()> {
116        let path = format!(
117            "/repositories/{}/{}/pullrequests/{}/approve",
118            workspace, repo_slug, pr_id
119        );
120        self.post_no_response(&path, &serde_json::json!({})).await
121    }
122
123    /// Unapprove a pull request
124    pub async fn unapprove_pull_request(
125        &self,
126        workspace: &str,
127        repo_slug: &str,
128        pr_id: u64,
129    ) -> Result<()> {
130        let path = format!(
131            "/repositories/{}/{}/pullrequests/{}/approve",
132            workspace, repo_slug, pr_id
133        );
134        self.delete(&path).await
135    }
136
137    /// Decline a pull request
138    pub async fn decline_pull_request(
139        &self,
140        workspace: &str,
141        repo_slug: &str,
142        pr_id: u64,
143    ) -> Result<PullRequest> {
144        let path = format!(
145            "/repositories/{}/{}/pullrequests/{}/decline",
146            workspace, repo_slug, pr_id
147        );
148        self.post(&path, &serde_json::json!({})).await
149    }
150
151    /// List comments on a pull request
152    pub async fn list_pr_comments(
153        &self,
154        workspace: &str,
155        repo_slug: &str,
156        pr_id: u64,
157    ) -> Result<Paginated<PullRequestComment>> {
158        let path = format!(
159            "/repositories/{}/{}/pullrequests/{}/comments",
160            workspace, repo_slug, pr_id
161        );
162        self.get(&path).await
163    }
164
165    /// Get a specific comment on a pull request
166    pub async fn get_pr_comment(
167        &self,
168        workspace: &str,
169        repo_slug: &str,
170        pr_id: u64,
171        comment_id: u64,
172    ) -> Result<PullRequestComment> {
173        let path = format!(
174            "/repositories/{}/{}/pullrequests/{}/comments/{}",
175            workspace, repo_slug, pr_id, comment_id
176        );
177        self.get(&path).await
178    }
179
180    /// Add a comment to a pull request
181    pub async fn add_pr_comment(
182        &self,
183        workspace: &str,
184        repo_slug: &str,
185        pr_id: u64,
186        content: &str,
187    ) -> Result<PullRequestComment> {
188        #[derive(serde::Serialize)]
189        struct CommentRequest {
190            content: ContentRequest,
191        }
192
193        #[derive(serde::Serialize)]
194        struct ContentRequest {
195            raw: String,
196        }
197
198        let request = CommentRequest {
199            content: ContentRequest {
200                raw: content.to_string(),
201            },
202        };
203
204        let path = format!(
205            "/repositories/{}/{}/pullrequests/{}/comments",
206            workspace, repo_slug, pr_id
207        );
208        self.post(&path, &request).await
209    }
210
211    /// Get the diff for a pull request
212    pub async fn get_pr_diff(
213        &self,
214        workspace: &str,
215        repo_slug: &str,
216        pr_id: u64,
217    ) -> Result<String> {
218        use reqwest::header::ACCEPT;
219
220        let path = format!(
221            "/repositories/{}/{}/pullrequests/{}/diff",
222            workspace, repo_slug, pr_id
223        );
224
225        let response = reqwest::Client::new()
226            .get(self.url(&path))
227            .header("Authorization", self.auth_header())
228            .header(ACCEPT, "text/plain")
229            .send()
230            .await?;
231
232        if response.status().is_success() {
233            Ok(response.text().await?)
234        } else {
235            anyhow::bail!("Failed to get diff: {}", response.status())
236        }
237    }
238}