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}