lmrc_gitlab/api/
projects.rs

1//! Project API operations.
2//!
3//! This module provides operations at the project level, including
4//! creating pipelines, managing project settings, and more.
5
6use crate::error::{GitLabError, Result};
7use crate::models::Pipeline;
8use gitlab::Gitlab;
9use gitlab::api::{
10    Query,
11    projects::pipelines::{self, PipelineVariable},
12};
13use std::collections::HashMap;
14
15/// Builder for project-level operations.
16///
17/// Provides operations that work at the project scope rather than
18/// on specific resources.
19///
20/// # Examples
21///
22/// ```no_run
23/// use lmrc_gitlab::GitLabClient;
24///
25/// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
26/// let client = GitLabClient::new("https://gitlab.com", "token")?;
27///
28/// // Trigger a new pipeline
29/// let pipeline = client.project("myorg/myproject")
30///     .create_pipeline()
31///     .ref_name("main")
32///     .trigger()
33///     .await?;
34/// # Ok(())
35/// # }
36/// ```
37pub struct ProjectBuilder<'a> {
38    client: &'a Gitlab,
39    project: String,
40}
41
42impl<'a> ProjectBuilder<'a> {
43    /// Creates a new project builder.
44    pub(crate) fn new(client: &'a Gitlab, project: impl Into<String>) -> Self {
45        Self {
46            client,
47            project: project.into(),
48        }
49    }
50
51    /// Create a builder for triggering a new pipeline.
52    ///
53    /// # Examples
54    ///
55    /// ```no_run
56    /// # use lmrc_gitlab::GitLabClient;
57    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
58    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
59    /// let pipeline = client.project("myorg/myproject")
60    ///     .create_pipeline()
61    ///     .ref_name("main")
62    ///     .trigger()
63    ///     .await?;
64    /// # Ok(())
65    /// # }
66    /// ```
67    pub fn create_pipeline(self) -> CreatePipelineBuilder<'a> {
68        CreatePipelineBuilder::new(self.client, self.project)
69    }
70}
71
72/// Builder for creating/triggering a new pipeline.
73///
74/// # Examples
75///
76/// ```no_run
77/// use lmrc_gitlab::GitLabClient;
78/// use std::collections::HashMap;
79///
80/// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
81/// let client = GitLabClient::new("https://gitlab.com", "token")?;
82///
83/// // Trigger pipeline with variables
84/// let mut vars = HashMap::new();
85/// vars.insert("DEPLOY_ENV".to_string(), "staging".to_string());
86///
87/// let pipeline = client.project("myorg/myproject")
88///     .create_pipeline()
89///     .ref_name("main")
90///     .variables(vars)
91///     .trigger()
92///     .await?;
93///
94/// println!("Created pipeline #{}", pipeline.id);
95/// # Ok(())
96/// # }
97/// ```
98pub struct CreatePipelineBuilder<'a> {
99    client: &'a Gitlab,
100    project: String,
101    ref_name: Option<String>,
102    variables: HashMap<String, String>,
103}
104
105impl<'a> CreatePipelineBuilder<'a> {
106    /// Creates a new pipeline creation builder.
107    pub(crate) fn new(client: &'a Gitlab, project: impl Into<String>) -> Self {
108        Self {
109            client,
110            project: project.into(),
111            ref_name: None,
112            variables: HashMap::new(),
113        }
114    }
115
116    /// Set the git reference (branch or tag) to run the pipeline on.
117    ///
118    /// # Examples
119    ///
120    /// ```no_run
121    /// # use lmrc_gitlab::GitLabClient;
122    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
123    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
124    /// let pipeline = client.project("myorg/myproject")
125    ///     .create_pipeline()
126    ///     .ref_name("develop")
127    ///     .trigger()
128    ///     .await?;
129    /// # Ok(())
130    /// # }
131    /// ```
132    pub fn ref_name(mut self, ref_name: impl Into<String>) -> Self {
133        self.ref_name = Some(ref_name.into());
134        self
135    }
136
137    /// Set CI/CD variables for the pipeline.
138    ///
139    /// # Examples
140    ///
141    /// ```no_run
142    /// # use lmrc_gitlab::GitLabClient;
143    /// # use std::collections::HashMap;
144    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
145    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
146    /// let mut vars = HashMap::new();
147    /// vars.insert("DEPLOY_TARGET".to_string(), "production".to_string());
148    ///
149    /// let pipeline = client.project("myorg/myproject")
150    ///     .create_pipeline()
151    ///     .ref_name("main")
152    ///     .variables(vars)
153    ///     .trigger()
154    ///     .await?;
155    /// # Ok(())
156    /// # }
157    /// ```
158    pub fn variables(mut self, variables: HashMap<String, String>) -> Self {
159        self.variables = variables;
160        self
161    }
162
163    /// Add a single CI/CD variable for the pipeline.
164    ///
165    /// # Examples
166    ///
167    /// ```no_run
168    /// # use lmrc_gitlab::GitLabClient;
169    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
170    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
171    /// let pipeline = client.project("myorg/myproject")
172    ///     .create_pipeline()
173    ///     .ref_name("main")
174    ///     .variable("ENVIRONMENT", "staging")
175    ///     .variable("DEBUG", "true")
176    ///     .trigger()
177    ///     .await?;
178    /// # Ok(())
179    /// # }
180    /// ```
181    pub fn variable(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
182        self.variables.insert(key.into(), value.into());
183        self
184    }
185
186    /// Trigger the pipeline.
187    ///
188    /// # Errors
189    ///
190    /// Returns [`GitLabError::InvalidInput`] if ref_name is not set.
191    /// Returns an error if the pipeline cannot be created.
192    ///
193    /// # Examples
194    ///
195    /// ```no_run
196    /// # use lmrc_gitlab::GitLabClient;
197    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
198    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
199    /// let pipeline = client.project("myorg/myproject")
200    ///     .create_pipeline()
201    ///     .ref_name("main")
202    ///     .trigger()
203    ///     .await?;
204    /// # Ok(())
205    /// # }
206    /// ```
207    pub async fn trigger(self) -> Result<Pipeline> {
208        let ref_name = self
209            .ref_name
210            .ok_or_else(|| GitLabError::invalid_input("ref", "ref_name is required"))?;
211
212        let mut endpoint_builder = pipelines::CreatePipeline::builder();
213        endpoint_builder.project(&self.project).ref_(&ref_name);
214
215        // Add variables if provided
216        for (key, value) in self.variables {
217            let var = PipelineVariable::builder()
218                .key(key)
219                .value(value)
220                .build()
221                .map_err(|e| {
222                    GitLabError::api(format!("Failed to build pipeline variable: {}", e))
223                })?;
224            endpoint_builder.variable(var);
225        }
226
227        let endpoint = endpoint_builder.build().map_err(|e| {
228            GitLabError::api(format!("Failed to build create pipeline endpoint: {}", e))
229        })?;
230
231        endpoint
232            .query(self.client)
233            .map_err(|e| GitLabError::api(format!("Failed to create pipeline: {}", e)))
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240
241    #[test]
242    fn test_project_builder() {
243        fn _compile_test(client: &Gitlab) {
244            let _builder = ProjectBuilder::new(client, "project");
245        }
246    }
247
248    #[test]
249    fn test_create_pipeline_builder() {
250        fn _compile_test(client: &Gitlab) {
251            let _builder = CreatePipelineBuilder::new(client, "project")
252                .ref_name("main")
253                .variable("KEY", "value");
254        }
255    }
256}