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 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 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}