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    /// Add a comment to a pull request
166    pub async fn add_pr_comment(
167        &self,
168        workspace: &str,
169        repo_slug: &str,
170        pr_id: u64,
171        content: &str,
172    ) -> Result<PullRequestComment> {
173        #[derive(serde::Serialize)]
174        struct CommentRequest {
175            content: ContentRequest,
176        }
177
178        #[derive(serde::Serialize)]
179        struct ContentRequest {
180            raw: String,
181        }
182
183        let request = CommentRequest {
184            content: ContentRequest {
185                raw: content.to_string(),
186            },
187        };
188
189        let path = format!(
190            "/repositories/{}/{}/pullrequests/{}/comments",
191            workspace, repo_slug, pr_id
192        );
193        self.post(&path, &request).await
194    }
195
196    /// Get the diff for a pull request
197    pub async fn get_pr_diff(
198        &self,
199        workspace: &str,
200        repo_slug: &str,
201        pr_id: u64,
202    ) -> Result<String> {
203        use reqwest::header::ACCEPT;
204
205        let path = format!(
206            "/repositories/{}/{}/pullrequests/{}/diff",
207            workspace, repo_slug, pr_id
208        );
209
210        let response = reqwest::Client::new()
211            .get(self.url(&path))
212            .header("Authorization", self.auth_header())
213            .header(ACCEPT, "text/plain")
214            .send()
215            .await?;
216
217        if response.status().is_success() {
218            Ok(response.text().await?)
219        } else {
220            anyhow::bail!("Failed to get diff: {}", response.status())
221        }
222    }
223}