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}