bitbucket_server_rs/api/build_status_post.rs
1//! # Build Status POST API
2//!
3//! This module provides functionality to post build status updates to Bitbucket Server.
4//! It allows setting the status of a build for a specific commit, which can be used
5//! to integrate CI/CD systems with Bitbucket Server.
6
7use crate::api::build_status::{BuildStatusState, TestResults};
8use crate::api::Api;
9use crate::client::{ApiRequest, ApiResponse, Client};
10use chrono::{serde::ts_seconds_option, DateTime, Utc};
11use serde::{Deserialize, Serialize};
12
13/// The payload for posting a build status update.
14///
15/// This struct represents the data that will be sent to the Bitbucket Server API
16/// when posting a build status update for a commit.
17#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct BuildStatusPostPayload {
20 /// The string referring to this branch plan/job.
21 ///
22 /// This is a unique identifier for the build status within the context of the repository.
23 pub key: String,
24
25 /// The build status state (SUCCESSFUL, FAILED, INPROGRESS, CANCELLED, or UNKNOWN).
26 pub state: BuildStatusState,
27
28 /// URL referring to the build result page in the CI tool.
29 ///
30 /// This URL will be linked from the Bitbucket Server UI.
31 pub url: String,
32
33 /// A unique identifier for this particular run of a plan.
34 ///
35 /// This can be used to track specific build runs.
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub build_number: Option<String>,
38
39 /// The date when the build status was added.
40 ///
41 /// If not provided, the current time will be used.
42 #[serde(skip_serializing_if = "Option::is_none", with = "ts_seconds_option")]
43 pub date_added: Option<DateTime<Utc>>,
44
45 /// A description of the build result.
46 ///
47 /// This can provide additional context about the build status.
48 #[serde(skip_serializing_if = "Option::is_none")]
49 pub description: Option<String>,
50
51 /// Duration of a completed build in milliseconds.
52 ///
53 /// This can be used to track build performance.
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub duration: Option<u64>,
56
57 /// A short string that describes the build plan.
58 ///
59 /// This is displayed in the Bitbucket Server UI.
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub name: Option<String>,
62
63 /// The identifier for the plan or job that ran the branch plan that produced this build status.
64 ///
65 /// This can be used to group related builds.
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub parent: Option<String>,
68
69 /// The fully qualified git reference e.g. refs/heads/master.
70 ///
71 /// This can be used to associate the build status with a specific branch or tag.
72 #[serde(skip_serializing_if = "Option::is_none", rename = "ref")]
73 pub reference: Option<String>,
74
75 /// A summary of the passed, failed and skipped tests.
76 ///
77 /// This can be used to provide test result information.
78 #[serde(skip_serializing_if = "Option::is_none")]
79 pub test_results: Option<TestResults>,
80}
81
82/// Request builder for posting a build status update.
83///
84/// This struct is used to build and send requests to post build status updates.
85#[derive(Debug)]
86pub struct BuildStatusPost {
87 /// The HTTP client to use for making requests
88 client: Client,
89
90 /// The key of the project containing the repository
91 project_key: String,
92
93 /// The ID of the commit to post the build status for
94 commit_id: String,
95
96 /// The slug of the repository
97 repository_slug: String,
98
99 /// The build status payload to post
100 build_status: BuildStatusPostPayload,
101}
102
103impl ApiRequest for BuildStatusPost {
104 // response has no content
105 type Output = ();
106
107 /// Sends the request to post a build status update.
108 ///
109 /// # Returns
110 ///
111 /// A Result indicating success or failure.
112 async fn send(&self) -> ApiResponse<Self::Output> {
113 let request_uri = format!(
114 "api/latest/projects/{}/repos/{}/commits/{}/builds",
115 self.project_key, self.repository_slug, self.commit_id
116 );
117
118 self.client
119 .post::<Self>(
120 &request_uri,
121 &serde_json::to_string(&self.build_status).unwrap(),
122 )
123 .await
124 }
125}
126
127impl Api {
128 /// Creates a request to post a build status update for a commit.
129 ///
130 /// This method returns a builder that can be used to send a request
131 /// to post a build status update for a commit.
132 ///
133 /// # Arguments
134 ///
135 /// * `project_key` - The key of the project containing the repository
136 /// * `repository_slug` - The slug of the repository
137 /// * `commit_id` - The ID of the commit to post the build status for
138 /// * `build_status` - The build status payload to post
139 ///
140 /// # Returns
141 ///
142 /// A builder for sending the request
143 ///
144 /// # Example
145 ///
146 /// ```no_run
147 /// use bitbucket_server_rs::client::{new, ApiRequest};
148 /// use bitbucket_server_rs::api::build_status::BuildStatusState;
149 /// use bitbucket_server_rs::api::build_status_post::BuildStatusPostPayload;
150 ///
151 /// #[tokio::main]
152 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
153 /// let client = new("https://bitbucket-server/rest", "API_TOKEN");
154 ///
155 /// // Create build status payload
156 /// let build_status = BuildStatusPostPayload {
157 /// key: "build-123".to_string(),
158 /// state: BuildStatusState::Successful,
159 /// url: "https://ci.example.com/build/123".to_string(),
160 /// description: Some("Build passed successfully".to_string()),
161 /// name: Some("CI Build".to_string()),
162 /// ..Default::default()
163 /// };
164 ///
165 /// // Post the build status
166 /// let response = client
167 /// .api()
168 /// .build_status_post(
169 /// "PROJECT_KEY",
170 /// "REPOSITORY_SLUG",
171 /// "COMMIT_ID",
172 /// &build_status
173 /// )
174 /// .send()
175 /// .await?;
176 ///
177 /// println!("Build status posted successfully");
178 ///
179 /// Ok(())
180 /// }
181 /// ```
182 ///
183 /// # Notes
184 ///
185 /// * The authenticated user must have REPO_READ permission for the repository that this build
186 /// status is for. The request can also be made with anonymous 2-legged OAuth.
187 ///
188 /// See [Bitbucket Data Center REST API Docs](https://developer.atlassian.com/server/bitbucket/rest/v811/api-group-builds-and-deployments/#api-api-latest-projects-projectkey-repos-repositoryslug-commits-commitid-builds-post)
189 pub fn build_status_post(
190 self,
191 project_key: &str,
192 repository_slug: &str,
193 commit_id: &str,
194 build_status: &BuildStatusPostPayload
195 ) -> BuildStatusPost {
196 BuildStatusPost {
197 client: self.client,
198 project_key: project_key.to_owned(),
199 commit_id: commit_id.to_owned(),
200 repository_slug: repository_slug.to_owned(),
201 build_status: build_status.to_owned()
202 }
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn it_can_serialize() {
212 let build_status = BuildStatusPostPayload {
213 key: "KEY".to_string(),
214 state: BuildStatusState::Successful,
215 url: "URL".to_string(),
216 build_number: Some("1".to_string()),
217 date_added: Some(
218 chrono::DateTime::parse_from_rfc3339("2025-01-30T01:02:03Z")
219 .unwrap()
220 .with_timezone(&Utc),
221 ),
222 description: Some("DESCRIPTION".to_string()),
223 duration: Some(12),
224 name: Some("NAME".to_string()),
225 parent: Some("PARENT".to_string()),
226 reference: Some("REF".to_string()),
227 test_results: Some(TestResults {
228 failed: 2,
229 successful: 3,
230 skipped: 1,
231 }),
232 };
233
234 let json = serde_json::to_string(&build_status).unwrap();
235 assert_eq!(
236 json,
237 r#"{"key":"KEY","state":"SUCCESSFUL","url":"URL","buildNumber":"1","dateAdded":1738198923,"description":"DESCRIPTION","duration":12,"name":"NAME","parent":"PARENT","ref":"REF","testResults":{"failed":2,"successful":3,"skipped":1}}"#
238 );
239 } // it_can_serialize
240
241 #[test]
242 fn it_can_serialize_partially() {
243 let build_status = BuildStatusPostPayload {
244 key: "KEY".to_string(),
245 state: BuildStatusState::Successful,
246 url: "URL".to_string(),
247 build_number: None,
248 date_added: None,
249 description: None,
250 duration: None,
251 name: None,
252 parent: None,
253 reference: None,
254 test_results: None,
255 };
256
257 let json = serde_json::to_string(&build_status).unwrap();
258 assert_eq!(json, r#"{"key":"KEY","state":"SUCCESSFUL","url":"URL"}"#);
259 } // it_can_serialize_partially
260}