bitbucket_server_rs/api/pull_request_post.rs
1//! # Pull Request POST API
2//!
3//! This module provides functionality to create pull requests in Bitbucket Server.
4//! It allows creating pull requests between branches with customizable titles,
5//! descriptions, and reviewers.
6
7use crate::api::Api;
8use crate::client::{ApiRequest, ApiResponse, Client};
9use serde::{Deserialize, Serialize};
10
11/// A user or group that can be added as a reviewer to a pull request
12#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
13pub struct Reviewer {
14 /// The user or group ID
15 pub user: User,
16}
17
18/// A user in Bitbucket Server
19#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
20pub struct User {
21 /// The name of the user
22 pub name: String,
23}
24
25/// The payload for creating a pull request.
26///
27/// This struct represents the data that will be sent to the Bitbucket Server API
28/// when creating a new pull request.
29#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct PullRequestPostPayload {
32 /// The title of the pull request
33 pub title: String,
34
35 /// The description of the pull request
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub description: Option<String>,
38
39 /// The branch information for the pull request
40 pub from_ref: RefInfo,
41
42 /// The target branch information for the pull request
43 pub to_ref: RefInfo,
44
45 /// The list of reviewers for the pull request
46 #[serde(skip_serializing_if = "Option::is_none")]
47 pub reviewers: Option<Vec<Reviewer>>,
48}
49
50/// Information about a Git reference (branch)
51#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
52#[serde(rename_all = "camelCase")]
53pub struct RefInfo {
54 /// The ID of the reference (usually branch name)
55 pub id: String,
56
57 /// The repository the branch belongs to
58 pub repository: RepositoryInfo,
59}
60
61/// Information about a repository
62#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct RepositoryInfo {
65 /// The slug of the repository
66 pub slug: String,
67
68 /// The project the repository belongs to
69 pub project: ProjectInfo,
70}
71
72/// Information about a project
73#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct ProjectInfo {
76 /// The key of the project
77 pub key: String,
78}
79
80/// Request builder for creating a pull request.
81///
82/// This struct is used to build and send requests to create pull requests.
83#[derive(Debug)]
84pub struct PullRequestPost {
85 /// The HTTP client to use for making requests
86 client: Client,
87
88 /// The key of the project containing the repository
89 project_key: String,
90
91 /// The slug of the repository
92 repository_slug: String,
93
94 /// The pull request payload to post
95 pull_request: PullRequestPostPayload,
96}
97
98impl ApiRequest for PullRequestPost {
99 type Output = PullRequestPostPayload;
100
101 /// Sends the request to create a pull request.
102 ///
103 /// # Returns
104 ///
105 /// A Result containing the created pull request information or an error.
106 async fn send(&self) -> ApiResponse<Self::Output> {
107 let request_uri = format!(
108 "api/latest/projects/{}/repos/{}/pull-requests",
109 self.project_key, self.repository_slug
110 );
111
112 self.client
113 .post::<Self>(
114 &request_uri,
115 &serde_json::to_string(&self.pull_request).unwrap(),
116 )
117 .await
118 }
119}
120
121impl Api {
122 /// Creates a request to create a new pull request.
123 ///
124 /// This method returns a builder that can be used to send a request
125 /// to create a new pull request.
126 ///
127 /// # Arguments
128 ///
129 /// * `project_key` - The key of the project containing the repository
130 /// * `repository_slug` - The slug of the repository
131 /// * `pull_request` - The pull request payload
132 ///
133 /// # Returns
134 ///
135 /// A builder for sending the request
136 ///
137 /// # Example
138 ///
139 /// ```no_run
140 /// use bitbucket_server_rs::client::{new, ApiRequest};
141 /// use bitbucket_server_rs::api::pull_request_post::{
142 /// PullRequestPostPayload, RefInfo, RepositoryInfo, ProjectInfo
143 /// };
144 ///
145 /// #[tokio::main]
146 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
147 /// let client = new("https://bitbucket-server/rest", "API_TOKEN");
148 ///
149 /// // Create pull request payload
150 /// let pull_request = PullRequestPostPayload {
151 /// title: "Add new feature".to_string(),
152 /// description: Some("Implements the new feature".to_string()),
153 /// from_ref: RefInfo {
154 /// id: "refs/heads/feature-branch".to_string(),
155 /// repository: RepositoryInfo {
156 /// slug: "my-repo".to_string(),
157 /// project: ProjectInfo {
158 /// key: "PROJECT".to_string(),
159 /// },
160 /// },
161 /// },
162 /// to_ref: RefInfo {
163 /// id: "refs/heads/main".to_string(),
164 /// repository: RepositoryInfo {
165 /// slug: "my-repo".to_string(),
166 /// project: ProjectInfo {
167 /// key: "PROJECT".to_string(),
168 /// },
169 /// },
170 /// },
171 /// reviewers: None,
172 /// };
173 ///
174 /// // Create the pull request
175 /// let response = client
176 /// .api()
177 /// .pull_request_post(
178 /// "PROJECT",
179 /// "my-repo",
180 /// &pull_request
181 /// )
182 /// .send()
183 /// .await?;
184 ///
185 /// println!("Pull request created successfully");
186 ///
187 /// Ok(())
188 /// }
189 /// ```
190 ///
191 /// # Notes
192 ///
193 /// * The authenticated user must have REPO_WRITE permission for the repository to create pull requests.
194 ///
195 /// See [Bitbucket Data Center REST API Docs](https://developer.atlassian.com/server/bitbucket/rest/v811/api-group-pull-requests/#api-api-latest-projects-projectkey-repos-repositoryslug-pull-requests-post)
196 pub fn pull_request_post(
197 self,
198 project_key: &str,
199 repository_slug: &str,
200 pull_request: &PullRequestPostPayload,
201 ) -> PullRequestPost {
202 PullRequestPost {
203 client: self.client,
204 project_key: project_key.to_owned(),
205 repository_slug: repository_slug.to_owned(),
206 pull_request: pull_request.to_owned(),
207 }
208 }
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn it_can_serialize() {
217 let pull_request = PullRequestPostPayload {
218 title: "Test PR".to_string(),
219 description: Some("Test description".to_string()),
220 from_ref: RefInfo {
221 id: "refs/heads/feature".to_string(),
222 repository: RepositoryInfo {
223 slug: "test-repo".to_string(),
224 project: ProjectInfo {
225 key: "TEST".to_string(),
226 },
227 },
228 },
229 to_ref: RefInfo {
230 id: "refs/heads/main".to_string(),
231 repository: RepositoryInfo {
232 slug: "test-repo".to_string(),
233 project: ProjectInfo {
234 key: "TEST".to_string(),
235 },
236 },
237 },
238 reviewers: Some(vec![Reviewer {
239 user: User {
240 name: "testuser".to_string(),
241 },
242 }]),
243 };
244
245 let json = serde_json::to_string(&pull_request).unwrap();
246 assert_eq!(
247 json,
248 r#"{"title":"Test PR","description":"Test description","fromRef":{"id":"refs/heads/feature","repository":{"slug":"test-repo","project":{"key":"TEST"}}},"toRef":{"id":"refs/heads/main","repository":{"slug":"test-repo","project":{"key":"TEST"}}},"reviewers":[{"user":{"name":"testuser"}}]}"#
249 );
250 }
251
252 #[test]
253 fn it_can_serialize_partially() {
254 let pull_request = PullRequestPostPayload {
255 title: "Test PR".to_string(),
256 description: None,
257 from_ref: RefInfo {
258 id: "refs/heads/feature".to_string(),
259 repository: RepositoryInfo {
260 slug: "test-repo".to_string(),
261 project: ProjectInfo {
262 key: "TEST".to_string(),
263 },
264 },
265 },
266 to_ref: RefInfo {
267 id: "refs/heads/main".to_string(),
268 repository: RepositoryInfo {
269 slug: "test-repo".to_string(),
270 project: ProjectInfo {
271 key: "TEST".to_string(),
272 },
273 },
274 },
275 reviewers: None,
276 };
277
278 let json = serde_json::to_string(&pull_request).unwrap();
279 assert_eq!(
280 json,
281 r#"{"title":"Test PR","fromRef":{"id":"refs/heads/feature","repository":{"slug":"test-repo","project":{"key":"TEST"}}},"toRef":{"id":"refs/heads/main","repository":{"slug":"test-repo","project":{"key":"TEST"}}}}"#
282 );
283 }
284}