lmrc_gitlab/api/variables.rs
1//! Project CI/CD variables API operations.
2//!
3//! This module provides operations for managing CI/CD variables
4//! at the project level.
5
6use crate::error::{GitLabError, Result};
7use crate::models::{Variable, VariableOptions, VariableType};
8use gitlab::Gitlab;
9use gitlab::api::{ApiError, Query, projects::variables};
10
11/// Builder for managing project CI/CD variables.
12///
13/// Provides operations to create, update, list, and delete
14/// CI/CD variables for a GitLab project.
15///
16/// # Examples
17///
18/// ```no_run
19/// use lmrc_gitlab::{GitLabClient, models::VariableOptions};
20///
21/// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
22/// let client = GitLabClient::new("https://gitlab.com", "token")?;
23///
24/// // Create a new variable
25/// let opts = VariableOptions::new()
26/// .protected(true)
27/// .masked(true);
28///
29/// client.variables("myorg/myproject")
30/// .create("API_KEY", "secret-value", opts)
31/// .await?;
32/// # Ok(())
33/// # }
34/// ```
35pub struct VariableBuilder<'a> {
36 client: &'a Gitlab,
37 project: String,
38}
39
40impl<'a> VariableBuilder<'a> {
41 /// Creates a new variable builder.
42 pub(crate) fn new(client: &'a Gitlab, project: impl Into<String>) -> Self {
43 Self {
44 client,
45 project: project.into(),
46 }
47 }
48
49 /// Create a new CI/CD variable.
50 ///
51 /// # Errors
52 ///
53 /// Returns an error if the variable already exists or if the request fails.
54 ///
55 /// # Examples
56 ///
57 /// ```no_run
58 /// # use lmrc_gitlab::{GitLabClient, models::VariableOptions};
59 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
60 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
61 /// let opts = VariableOptions::new()
62 /// .protected(true)
63 /// .masked(true);
64 ///
65 /// client.variables("myproject")
66 /// .create("DATABASE_URL", "postgres://...", opts)
67 /// .await?;
68 /// # Ok(())
69 /// # }
70 /// ```
71 pub async fn create(
72 &self,
73 key: impl AsRef<str>,
74 value: impl AsRef<str>,
75 opts: VariableOptions,
76 ) -> Result<Variable> {
77 let key = key.as_ref();
78 let value = value.as_ref();
79
80 // Build the request using the gitlab crate's API
81 let mut builder = variables::CreateProjectVariable::builder();
82 builder
83 .project(&self.project)
84 .key(key)
85 .value(value)
86 .variable_type(match opts.variable_type {
87 VariableType::EnvVar => variables::ProjectVariableType::EnvVar,
88 VariableType::File => variables::ProjectVariableType::File,
89 })
90 .protected(opts.protected)
91 .masked(opts.masked)
92 .raw(opts.raw);
93
94 if let Some(scope) = opts.environment_scope {
95 builder.environment_scope(scope);
96 }
97
98 if let Some(desc) = opts.description {
99 builder.description(desc);
100 }
101
102 let endpoint = builder.build().map_err(|e| {
103 GitLabError::api(format!("Failed to build create variable request: {}", e))
104 })?;
105
106 endpoint.query(self.client).map_err(|e| match e {
107 ApiError::GitlabService { status, .. } if status.as_u16() == 400 => {
108 GitLabError::conflict(format!("Variable '{}' already exists", key))
109 }
110 _ => GitLabError::api(format!("Failed to create variable '{}': {}", key, e)),
111 })
112 }
113
114 /// Update an existing CI/CD variable.
115 ///
116 /// # Errors
117 ///
118 /// Returns an error if the variable doesn't exist or if the request fails.
119 ///
120 /// # Examples
121 ///
122 /// ```no_run
123 /// # use lmrc_gitlab::{GitLabClient, models::VariableOptions};
124 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
125 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
126 /// let opts = VariableOptions::new()
127 /// .protected(true);
128 ///
129 /// client.variables("myproject")
130 /// .update("API_KEY", "new-secret-value", opts)
131 /// .await?;
132 /// # Ok(())
133 /// # }
134 /// ```
135 pub async fn update(
136 &self,
137 key: impl AsRef<str>,
138 value: impl AsRef<str>,
139 opts: VariableOptions,
140 ) -> Result<Variable> {
141 let key = key.as_ref();
142 let value = value.as_ref();
143
144 let mut builder = variables::UpdateProjectVariable::builder();
145 builder
146 .project(&self.project)
147 .key(key)
148 .value(value)
149 .variable_type(match opts.variable_type {
150 VariableType::EnvVar => variables::ProjectVariableType::EnvVar,
151 VariableType::File => variables::ProjectVariableType::File,
152 })
153 .protected(opts.protected)
154 .masked(opts.masked)
155 .raw(opts.raw);
156
157 if let Some(scope) = opts.environment_scope {
158 builder.environment_scope(scope);
159 }
160
161 if let Some(desc) = opts.description {
162 builder.description(desc);
163 }
164
165 let endpoint = builder.build().map_err(|e| {
166 GitLabError::api(format!("Failed to build update variable request: {}", e))
167 })?;
168
169 endpoint.query(self.client).map_err(|e| match e {
170 ApiError::GitlabService { status, .. } if status.as_u16() == 404 => {
171 GitLabError::not_found("Variable", key)
172 }
173 _ => GitLabError::api(format!("Failed to update variable '{}': {}", key, e)),
174 })
175 }
176
177 /// Create or update a CI/CD variable (upsert).
178 ///
179 /// If the variable exists, it will be updated. Otherwise, it will be created.
180 ///
181 /// # Examples
182 ///
183 /// ```no_run
184 /// # use lmrc_gitlab::{GitLabClient, models::VariableOptions};
185 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
186 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
187 /// let opts = VariableOptions::new()
188 /// .protected(true)
189 /// .masked(true);
190 ///
191 /// // Will create if doesn't exist, update if it does
192 /// client.variables("myproject")
193 /// .set("API_KEY", "secret-value", opts)
194 /// .await?;
195 /// # Ok(())
196 /// # }
197 /// ```
198 pub async fn set(
199 &self,
200 key: impl AsRef<str>,
201 value: impl AsRef<str>,
202 opts: VariableOptions,
203 ) -> Result<Variable> {
204 let key = key.as_ref();
205
206 // Try to update first
207 match self.update(key, value.as_ref(), opts.clone()).await {
208 Ok(var) => Ok(var),
209 Err(GitLabError::NotFound { .. }) => {
210 // Variable doesn't exist, create it
211 self.create(key, value.as_ref(), opts).await
212 }
213 Err(e) => Err(e),
214 }
215 }
216
217 /// Get a CI/CD variable.
218 ///
219 /// # Errors
220 ///
221 /// Returns an error if the variable doesn't exist or if the request fails.
222 ///
223 /// # Examples
224 ///
225 /// ```no_run
226 /// # use lmrc_gitlab::GitLabClient;
227 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
228 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
229 /// let var = client.variables("myproject")
230 /// .get("API_KEY")
231 /// .await?;
232 ///
233 /// println!("Value: {}", var.value);
234 /// # Ok(())
235 /// # }
236 /// ```
237 pub async fn get(&self, key: impl AsRef<str>) -> Result<Variable> {
238 let key = key.as_ref();
239
240 let endpoint = variables::ProjectVariable::builder()
241 .project(&self.project)
242 .key(key)
243 .build()
244 .map_err(|e| {
245 GitLabError::api(format!("Failed to build get variable request: {}", e))
246 })?;
247
248 endpoint.query(self.client).map_err(|e| match e {
249 ApiError::GitlabService { status, .. } if status.as_u16() == 404 => {
250 GitLabError::not_found("Variable", key)
251 }
252 _ => GitLabError::api(format!("Failed to get variable '{}': {}", key, e)),
253 })
254 }
255
256 /// List all CI/CD variables for the project.
257 ///
258 /// # Examples
259 ///
260 /// ```no_run
261 /// # use lmrc_gitlab::GitLabClient;
262 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
263 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
264 /// let vars = client.variables("myproject")
265 /// .list()
266 /// .await?;
267 ///
268 /// for var in vars {
269 /// println!("{}: {} (protected={})", var.key, var.value, var.protected);
270 /// }
271 /// # Ok(())
272 /// # }
273 /// ```
274 pub async fn list(&self) -> Result<Vec<Variable>> {
275 let endpoint = variables::ProjectVariables::builder()
276 .project(&self.project)
277 .build()
278 .map_err(|e| {
279 GitLabError::api(format!("Failed to build list variables request: {}", e))
280 })?;
281
282 endpoint
283 .query(self.client)
284 .map_err(|e| GitLabError::api(format!("Failed to list variables: {}", e)))
285 }
286
287 /// Delete a CI/CD variable.
288 ///
289 /// # Errors
290 ///
291 /// Returns an error if the variable doesn't exist or if the request fails.
292 ///
293 /// # Examples
294 ///
295 /// ```no_run
296 /// # use lmrc_gitlab::GitLabClient;
297 /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
298 /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
299 /// client.variables("myproject")
300 /// .delete("OLD_API_KEY")
301 /// .await?;
302 /// # Ok(())
303 /// # }
304 /// ```
305 pub async fn delete(&self, key: impl AsRef<str>) -> Result<()> {
306 let key = key.as_ref();
307
308 let endpoint = variables::DeleteProjectVariable::builder()
309 .project(&self.project)
310 .key(key)
311 .build()
312 .map_err(|e| {
313 GitLabError::api(format!("Failed to build delete variable request: {}", e))
314 })?;
315
316 // Delete endpoints don't return a value, use ignore() method
317 gitlab::api::ignore(endpoint)
318 .query(self.client)
319 .map_err(|e| match e {
320 ApiError::GitlabService { status, .. } if status.as_u16() == 404 => {
321 GitLabError::not_found("Variable", key)
322 }
323 _ => GitLabError::api(format!("Failed to delete variable '{}': {}", key, e)),
324 })?;
325
326 Ok(())
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[test]
335 fn test_variable_builder() {
336 fn _compile_test(client: &Gitlab) {
337 let _builder = VariableBuilder::new(client, "project");
338 }
339 }
340}