lmrc_gitlab/api/jobs.rs
1//! Job API operations with builder pattern.
2
3use crate::error::{GitLabError, Result};
4use crate::models::{Job, JobStatus};
5use gitlab::Gitlab;
6use gitlab::api::{Query, projects::jobs, projects::pipelines::PipelineJobs};
7
8/// Builder for job list operations.
9///
10/// Can list jobs for a project or for a specific pipeline.
11///
12/// # Examples
13///
14/// ```no_run
15/// use lmrc_gitlab::{GitLabClient, models::JobStatus};
16///
17/// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
18/// let client = GitLabClient::new("https://gitlab.com", "token")?;
19///
20/// // List all jobs in a project
21/// let jobs = client.jobs("myorg/myproject")
22/// .status(JobStatus::Failed)
23/// .list()
24/// .await?;
25///
26/// // List jobs in a specific pipeline
27/// let pipeline_jobs = client.jobs("myorg/myproject")
28/// .pipeline(12345)
29/// .list()
30/// .await?;
31/// # Ok(())
32/// # }
33/// ```
34pub struct JobListBuilder<'a> {
35 client: &'a Gitlab,
36 project: String,
37 pipeline_id: Option<u64>,
38 status: Option<JobStatus>,
39 limit: Option<usize>,
40}
41
42impl<'a> JobListBuilder<'a> {
43 /// Creates a new job list builder.
44 pub(crate) fn new(client: &'a Gitlab, project: impl Into<String>) -> Self {
45 Self {
46 client,
47 project: project.into(),
48 pipeline_id: None,
49 status: None,
50 limit: None,
51 }
52 }
53
54 /// Filter jobs by pipeline.
55 ///
56 /// # Examples
57 ///
58 /// ```no_run
59 /// # use lmrc_gitlab::GitLabClient;
60 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
61 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
62 /// let jobs = client.jobs("myorg/myproject")
63 /// .pipeline(12345)
64 /// .list()
65 /// .await?;
66 /// # Ok(())
67 /// # }
68 /// ```
69 pub fn pipeline(mut self, pipeline_id: u64) -> Self {
70 self.pipeline_id = Some(pipeline_id);
71 self
72 }
73
74 /// Filter jobs by status.
75 pub fn status(mut self, status: JobStatus) -> Self {
76 self.status = Some(status);
77 self
78 }
79
80 /// Limit the number of jobs returned.
81 pub fn limit(mut self, limit: usize) -> Self {
82 self.limit = Some(limit);
83 self
84 }
85
86 /// Execute the query and return the list of jobs.
87 ///
88 /// # Errors
89 ///
90 /// Returns an error if the API request fails.
91 pub async fn list(self) -> Result<Vec<Job>> {
92 let jobs: Vec<Job> = if let Some(pipeline_id) = self.pipeline_id {
93 // List jobs for a specific pipeline
94 let endpoint = PipelineJobs::builder()
95 .project(&self.project)
96 .pipeline(pipeline_id)
97 .build()
98 .map_err(|e| GitLabError::api(format!("Failed to build jobs endpoint: {}", e)))?;
99
100 endpoint
101 .query(self.client)
102 .map_err(|e| GitLabError::api(format!("Failed to query jobs: {}", e)))?
103 } else {
104 // List all jobs in project (requires different endpoint in real implementation)
105 // For now, return empty as this would need the Jobs endpoint
106 // which would list all jobs across all pipelines
107 vec![]
108 };
109
110 // Apply filters
111 let mut filtered = jobs;
112 if let Some(status) = self.status {
113 filtered.retain(|j| j.status == status);
114 }
115
116 if let Some(limit) = self.limit {
117 filtered.truncate(limit);
118 }
119
120 Ok(filtered)
121 }
122}
123
124/// Builder for single job operations.
125///
126/// Provides operations for a specific job including get, retry, cancel, and logs.
127///
128/// # Examples
129///
130/// ```no_run
131/// use lmrc_gitlab::GitLabClient;
132///
133/// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
134/// let client = GitLabClient::new("https://gitlab.com", "token")?;
135///
136/// // Get job details
137/// let job = client.job("myorg/myproject", 67890).get().await?;
138///
139/// // Retry a failed job
140/// client.job("myorg/myproject", 67890).retry().await?;
141///
142/// // Get job logs
143/// let logs = client.job("myorg/myproject", 67890).logs().await?;
144/// # Ok(())
145/// # }
146/// ```
147pub struct JobBuilder<'a> {
148 client: &'a Gitlab,
149 project: String,
150 job_id: u64,
151}
152
153impl<'a> JobBuilder<'a> {
154 /// Creates a new job builder for a specific job.
155 pub(crate) fn new(client: &'a Gitlab, project: impl Into<String>, job_id: u64) -> Self {
156 Self {
157 client,
158 project: project.into(),
159 job_id,
160 }
161 }
162
163 /// Get the job details.
164 ///
165 /// # Errors
166 ///
167 /// Returns [`GitLabError::NotFound`] if the job doesn't exist.
168 pub async fn get(self) -> Result<Job> {
169 let endpoint = jobs::Job::builder()
170 .project(&self.project)
171 .job(self.job_id)
172 .build()
173 .map_err(|e| GitLabError::api(format!("Failed to build job endpoint: {}", e)))?;
174
175 endpoint.query(self.client).map_err(|e| match e {
176 gitlab::api::ApiError::GitlabService { status, .. } if status.as_u16() == 404 => {
177 GitLabError::not_found("job", self.job_id)
178 }
179 _ => GitLabError::api(format!("Failed to get job: {}", e)),
180 })
181 }
182
183 /// Retry the job.
184 ///
185 /// Creates a new job based on the original job.
186 ///
187 /// # Errors
188 ///
189 /// Returns an error if the job cannot be retried.
190 pub async fn retry(self) -> Result<Job> {
191 let endpoint = jobs::RetryJob::builder()
192 .project(&self.project)
193 .job(self.job_id)
194 .build()
195 .map_err(|e| GitLabError::api(format!("Failed to build retry endpoint: {}", e)))?;
196
197 endpoint
198 .query(self.client)
199 .map_err(|e| GitLabError::api(format!("Failed to retry job: {}", e)))
200 }
201
202 /// Cancel the job.
203 ///
204 /// Stops a running or pending job.
205 ///
206 /// # Errors
207 ///
208 /// Returns an error if the job cannot be canceled.
209 pub async fn cancel(self) -> Result<Job> {
210 let endpoint = jobs::CancelJob::builder()
211 .project(&self.project)
212 .job(self.job_id)
213 .build()
214 .map_err(|e| GitLabError::api(format!("Failed to build cancel endpoint: {}", e)))?;
215
216 endpoint
217 .query(self.client)
218 .map_err(|e| GitLabError::api(format!("Failed to cancel job: {}", e)))
219 }
220
221 /// Get the job logs (trace).
222 ///
223 /// # Errors
224 ///
225 /// Returns an error if logs cannot be retrieved.
226 pub async fn logs(self) -> Result<String> {
227 let endpoint = jobs::JobTrace::builder()
228 .project(&self.project)
229 .job(self.job_id)
230 .build()
231 .map_err(|e| GitLabError::api(format!("Failed to build trace endpoint: {}", e)))?;
232
233 endpoint
234 .query(self.client)
235 .map_err(|e| GitLabError::api(format!("Failed to get job logs: {}", e)))
236 }
237
238 /// Download job artifacts.
239 ///
240 /// Returns the raw artifact data as bytes.
241 ///
242 /// # Errors
243 ///
244 /// Returns an error if artifacts don't exist or cannot be downloaded.
245 pub async fn artifacts(self) -> Result<Vec<u8>> {
246 // Note: This would require the artifacts endpoint
247 // For now, return an error indicating not implemented
248 Err(GitLabError::api("Artifact download not yet implemented"))
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_job_list_builder() {
258 fn _compile_test(client: &Gitlab) {
259 let _builder = JobListBuilder::new(client, "project")
260 .pipeline(123)
261 .status(JobStatus::Failed)
262 .limit(10);
263 }
264 }
265
266 #[test]
267 fn test_job_builder() {
268 fn _compile_test(client: &Gitlab) {
269 let _builder = JobBuilder::new(client, "project", 456);
270 }
271 }
272}