1use anyhow::Result;
2
3use super::BitbucketClient;
4use crate::models::{
5 CreatePullRequestRequest, MergePullRequestRequest, Paginated, PullRequest, PullRequestComment,
6 PullRequestState,
7};
8
9impl BitbucketClient {
10 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 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 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 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 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 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 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 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 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 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 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 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}