lmrc_gitlab/api/pipelines.rs
1//! Pipeline API operations with builder pattern.
2
3use crate::error::{GitLabError, Result};
4use crate::models::{Pipeline, PipelineBasic, PipelineStatus};
5use gitlab::Gitlab;
6use gitlab::api::{Query, projects::pipelines};
7
8/// Builder for pipeline list operations.
9///
10/// Provides a fluent API for querying pipelines with various filters.
11///
12/// # Examples
13///
14/// ```no_run
15/// use lmrc_gitlab::{GitLabClient, models::PipelineStatus};
16///
17/// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
18/// let client = GitLabClient::new("https://gitlab.com", "token")?;
19///
20/// // List failed pipelines on main branch
21/// let pipelines = client.pipelines("myorg/myproject")
22/// .status(PipelineStatus::Failed)
23/// .ref_name("main")
24/// .limit(10)
25/// .list()
26/// .await?;
27/// # Ok(())
28/// # }
29/// ```
30pub struct PipelineListBuilder<'a> {
31 client: &'a Gitlab,
32 project: String,
33 status: Option<PipelineStatus>,
34 ref_name: Option<String>,
35 limit: Option<usize>,
36}
37
38impl<'a> PipelineListBuilder<'a> {
39 /// Creates a new pipeline list builder.
40 pub(crate) fn new(client: &'a Gitlab, project: impl Into<String>) -> Self {
41 Self {
42 client,
43 project: project.into(),
44 status: None,
45 ref_name: None,
46 limit: None,
47 }
48 }
49
50 /// Filter pipelines by status.
51 ///
52 /// # Examples
53 ///
54 /// ```no_run
55 /// # use lmrc_gitlab::{GitLabClient, models::PipelineStatus};
56 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
57 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
58 /// let pipelines = client.pipelines("myorg/myproject")
59 /// .status(PipelineStatus::Failed)
60 /// .list()
61 /// .await?;
62 /// # Ok(())
63 /// # }
64 /// ```
65 pub fn status(mut self, status: PipelineStatus) -> Self {
66 self.status = Some(status);
67 self
68 }
69
70 /// Filter pipelines by git reference (branch or tag).
71 ///
72 /// # Examples
73 ///
74 /// ```no_run
75 /// # use lmrc_gitlab::GitLabClient;
76 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
77 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
78 /// let pipelines = client.pipelines("myorg/myproject")
79 /// .ref_name("main")
80 /// .list()
81 /// .await?;
82 /// # Ok(())
83 /// # }
84 /// ```
85 pub fn ref_name(mut self, ref_name: impl Into<String>) -> Self {
86 self.ref_name = Some(ref_name.into());
87 self
88 }
89
90 /// Limit the number of pipelines returned.
91 ///
92 /// # Examples
93 ///
94 /// ```no_run
95 /// # use lmrc_gitlab::GitLabClient;
96 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
97 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
98 /// let pipelines = client.pipelines("myorg/myproject")
99 /// .limit(5)
100 /// .list()
101 /// .await?;
102 /// # Ok(())
103 /// # }
104 /// ```
105 pub fn limit(mut self, limit: usize) -> Self {
106 self.limit = Some(limit);
107 self
108 }
109
110 /// Execute the query and return the list of pipelines.
111 ///
112 /// # Errors
113 ///
114 /// Returns an error if the API request fails or if the project is not found.
115 pub async fn list(self) -> Result<Vec<PipelineBasic>> {
116 let endpoint = pipelines::Pipelines::builder()
117 .project(&self.project)
118 .build()
119 .map_err(|e| GitLabError::api(format!("Failed to build pipeline endpoint: {}", e)))?;
120
121 let mut pipelines: Vec<PipelineBasic> = endpoint
122 .query(self.client)
123 .map_err(|e| GitLabError::api(format!("Failed to query pipelines: {}", e)))?;
124
125 // Apply filters
126 if let Some(status) = self.status {
127 pipelines.retain(|p| p.status == status);
128 }
129
130 if let Some(ref_name) = self.ref_name {
131 pipelines.retain(|p| p.ref_name == ref_name);
132 }
133
134 // Apply limit
135 if let Some(limit) = self.limit {
136 pipelines.truncate(limit);
137 }
138
139 Ok(pipelines)
140 }
141}
142
143/// Builder for single pipeline operations.
144///
145/// Provides operations for a specific pipeline including get, retry, and cancel.
146///
147/// # Examples
148///
149/// ```no_run
150/// use lmrc_gitlab::GitLabClient;
151///
152/// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
153/// let client = GitLabClient::new("https://gitlab.com", "token")?;
154///
155/// // Get pipeline details
156/// let pipeline = client.pipeline("myorg/myproject", 12345)
157/// .get()
158/// .await?;
159///
160/// println!("Pipeline status: {}", pipeline.status);
161/// # Ok(())
162/// # }
163/// ```
164pub struct PipelineBuilder<'a> {
165 client: &'a Gitlab,
166 project: String,
167 pipeline_id: u64,
168}
169
170impl<'a> PipelineBuilder<'a> {
171 /// Creates a new pipeline builder for a specific pipeline.
172 pub(crate) fn new(client: &'a Gitlab, project: impl Into<String>, pipeline_id: u64) -> Self {
173 Self {
174 client,
175 project: project.into(),
176 pipeline_id,
177 }
178 }
179
180 /// Get the pipeline details.
181 ///
182 /// # Errors
183 ///
184 /// Returns [`GitLabError::NotFound`] if the pipeline doesn't exist.
185 ///
186 /// # Examples
187 ///
188 /// ```no_run
189 /// # use lmrc_gitlab::GitLabClient;
190 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
191 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
192 /// let pipeline = client.pipeline("myorg/myproject", 12345)
193 /// .get()
194 /// .await?;
195 /// # Ok(())
196 /// # }
197 /// ```
198 pub async fn get(self) -> Result<Pipeline> {
199 let endpoint = pipelines::Pipeline::builder()
200 .project(&self.project)
201 .pipeline(self.pipeline_id)
202 .build()
203 .map_err(|e| GitLabError::api(format!("Failed to build pipeline endpoint: {}", e)))?;
204
205 endpoint.query(self.client).map_err(|e| match e {
206 gitlab::api::ApiError::GitlabService { status, .. } if status.as_u16() == 404 => {
207 GitLabError::not_found("pipeline", self.pipeline_id)
208 }
209 _ => GitLabError::api(format!("Failed to get pipeline: {}", e)),
210 })
211 }
212
213 /// Retry the pipeline.
214 ///
215 /// This creates a new pipeline based on the failed jobs of the original pipeline.
216 ///
217 /// # Errors
218 ///
219 /// Returns an error if the pipeline cannot be retried (e.g., it's still running).
220 ///
221 /// # Examples
222 ///
223 /// ```no_run
224 /// # use lmrc_gitlab::GitLabClient;
225 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
226 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
227 /// let pipeline = client.pipeline("myorg/myproject", 12345)
228 /// .retry()
229 /// .await?;
230 /// # Ok(())
231 /// # }
232 /// ```
233 pub async fn retry(self) -> Result<Pipeline> {
234 let endpoint = pipelines::RetryPipeline::builder()
235 .project(&self.project)
236 .pipeline(self.pipeline_id)
237 .build()
238 .map_err(|e| GitLabError::api(format!("Failed to build retry endpoint: {}", e)))?;
239
240 endpoint
241 .query(self.client)
242 .map_err(|e| GitLabError::api(format!("Failed to retry pipeline: {}", e)))
243 }
244
245 /// Cancel the pipeline.
246 ///
247 /// Stops all running jobs in the pipeline.
248 ///
249 /// # Errors
250 ///
251 /// Returns an error if the pipeline cannot be canceled (e.g., already finished).
252 ///
253 /// # Examples
254 ///
255 /// ```no_run
256 /// # use lmrc_gitlab::GitLabClient;
257 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
258 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
259 /// let pipeline = client.pipeline("myorg/myproject", 12345)
260 /// .cancel()
261 /// .await?;
262 /// # Ok(())
263 /// # }
264 /// ```
265 pub async fn cancel(self) -> Result<Pipeline> {
266 let endpoint = pipelines::CancelPipeline::builder()
267 .project(&self.project)
268 .pipeline(self.pipeline_id)
269 .build()
270 .map_err(|e| GitLabError::api(format!("Failed to build cancel endpoint: {}", e)))?;
271
272 endpoint
273 .query(self.client)
274 .map_err(|e| GitLabError::api(format!("Failed to cancel pipeline: {}", e)))
275 }
276}
277
278/// High-level pipeline API.
279///
280/// This struct provides the entry point for pipeline operations.
281pub struct PipelineApi;
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_pipeline_list_builder() {
289 // This is a compile-time test to ensure the API is ergonomic
290 fn _compile_test(client: &Gitlab) {
291 let _builder = PipelineListBuilder::new(client, "project")
292 .status(PipelineStatus::Failed)
293 .ref_name("main")
294 .limit(10);
295 }
296 }
297
298 #[test]
299 fn test_pipeline_builder() {
300 // Compile-time test for single pipeline operations
301 fn _compile_test(client: &Gitlab) {
302 let _builder = PipelineBuilder::new(client, "project", 123);
303 }
304 }
305}