lmrc-gitlab 0.3.16

GitLab API client library for the LMRC Stack - comprehensive Rust library for programmatic control of GitLab via its API
Documentation
//! Project CI/CD variables API operations.
//!
//! This module provides operations for managing CI/CD variables
//! at the project level.

use crate::error::{GitLabError, Result};
use crate::models::{Variable, VariableOptions, VariableType};
use gitlab::Gitlab;
use gitlab::api::{ApiError, Query, projects::variables};

/// Builder for managing project CI/CD variables.
///
/// Provides operations to create, update, list, and delete
/// CI/CD variables for a GitLab project.
///
/// # Examples
///
/// ```no_run
/// use lmrc_gitlab::{GitLabClient, models::VariableOptions};
///
/// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
/// let client = GitLabClient::new("https://gitlab.com", "token")?;
///
/// // Create a new variable
/// let opts = VariableOptions::new()
///     .protected(true)
///     .masked(true);
///
/// client.variables("myorg/myproject")
///     .create("API_KEY", "secret-value", opts)
///     .await?;
/// # Ok(())
/// # }
/// ```
pub struct VariableBuilder<'a> {
    client: &'a Gitlab,
    project: String,
}

impl<'a> VariableBuilder<'a> {
    /// Creates a new variable builder.
    pub(crate) fn new(client: &'a Gitlab, project: impl Into<String>) -> Self {
        Self {
            client,
            project: project.into(),
        }
    }

    /// Create a new CI/CD variable.
    ///
    /// # Errors
    ///
    /// Returns an error if the variable already exists or if the request fails.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::{GitLabClient, models::VariableOptions};
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let opts = VariableOptions::new()
    ///     .protected(true)
    ///     .masked(true);
    ///
    /// client.variables("myproject")
    ///     .create("DATABASE_URL", "postgres://...", opts)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn create(
        &self,
        key: impl AsRef<str>,
        value: impl AsRef<str>,
        opts: VariableOptions,
    ) -> Result<Variable> {
        let key = key.as_ref();
        let value = value.as_ref();

        // Build the request using the gitlab crate's API
        let mut builder = variables::CreateProjectVariable::builder();
        builder
            .project(&self.project)
            .key(key)
            .value(value)
            .variable_type(match opts.variable_type {
                VariableType::EnvVar => variables::ProjectVariableType::EnvVar,
                VariableType::File => variables::ProjectVariableType::File,
            })
            .protected(opts.protected)
            .masked(opts.masked)
            .raw(opts.raw);

        if let Some(scope) = opts.environment_scope {
            builder.environment_scope(scope);
        }

        if let Some(desc) = opts.description {
            builder.description(desc);
        }

        let endpoint = builder.build().map_err(|e| {
            GitLabError::api(format!("Failed to build create variable request: {}", e))
        })?;

        endpoint.query(self.client).map_err(|e| match e {
            ApiError::GitlabService { status, .. } if status.as_u16() == 400 => {
                GitLabError::conflict(format!("Variable '{}' already exists", key))
            }
            _ => GitLabError::api(format!("Failed to create variable '{}': {}", key, e)),
        })
    }

    /// Update an existing CI/CD variable.
    ///
    /// # Errors
    ///
    /// Returns an error if the variable doesn't exist or if the request fails.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::{GitLabClient, models::VariableOptions};
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let opts = VariableOptions::new()
    ///     .protected(true);
    ///
    /// client.variables("myproject")
    ///     .update("API_KEY", "new-secret-value", opts)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn update(
        &self,
        key: impl AsRef<str>,
        value: impl AsRef<str>,
        opts: VariableOptions,
    ) -> Result<Variable> {
        let key = key.as_ref();
        let value = value.as_ref();

        let mut builder = variables::UpdateProjectVariable::builder();
        builder
            .project(&self.project)
            .key(key)
            .value(value)
            .variable_type(match opts.variable_type {
                VariableType::EnvVar => variables::ProjectVariableType::EnvVar,
                VariableType::File => variables::ProjectVariableType::File,
            })
            .protected(opts.protected)
            .masked(opts.masked)
            .raw(opts.raw);

        if let Some(scope) = opts.environment_scope {
            builder.environment_scope(scope);
        }

        if let Some(desc) = opts.description {
            builder.description(desc);
        }

        let endpoint = builder.build().map_err(|e| {
            GitLabError::api(format!("Failed to build update variable request: {}", e))
        })?;

        endpoint.query(self.client).map_err(|e| match e {
            ApiError::GitlabService { status, .. } if status.as_u16() == 404 => {
                GitLabError::not_found("Variable", key)
            }
            _ => GitLabError::api(format!("Failed to update variable '{}': {}", key, e)),
        })
    }

    /// Create or update a CI/CD variable (upsert).
    ///
    /// If the variable exists, it will be updated. Otherwise, it will be created.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::{GitLabClient, models::VariableOptions};
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let opts = VariableOptions::new()
    ///     .protected(true)
    ///     .masked(true);
    ///
    /// // Will create if doesn't exist, update if it does
    /// client.variables("myproject")
    ///     .set("API_KEY", "secret-value", opts)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn set(
        &self,
        key: impl AsRef<str>,
        value: impl AsRef<str>,
        opts: VariableOptions,
    ) -> Result<Variable> {
        let key = key.as_ref();

        // Try to update first
        match self.update(key, value.as_ref(), opts.clone()).await {
            Ok(var) => Ok(var),
            Err(GitLabError::NotFound { .. }) => {
                // Variable doesn't exist, create it
                self.create(key, value.as_ref(), opts).await
            }
            Err(e) => Err(e),
        }
    }

    /// Get a CI/CD variable.
    ///
    /// # Errors
    ///
    /// Returns an error if the variable doesn't exist or if the request fails.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::GitLabClient;
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let var = client.variables("myproject")
    ///     .get("API_KEY")
    ///     .await?;
    ///
    /// println!("Value: {}", var.value);
    /// # Ok(())
    /// # }
    /// ```
    pub async fn get(&self, key: impl AsRef<str>) -> Result<Variable> {
        let key = key.as_ref();

        let endpoint = variables::ProjectVariable::builder()
            .project(&self.project)
            .key(key)
            .build()
            .map_err(|e| {
                GitLabError::api(format!("Failed to build get variable request: {}", e))
            })?;

        endpoint.query(self.client).map_err(|e| match e {
            ApiError::GitlabService { status, .. } if status.as_u16() == 404 => {
                GitLabError::not_found("Variable", key)
            }
            _ => GitLabError::api(format!("Failed to get variable '{}': {}", key, e)),
        })
    }

    /// List all CI/CD variables for the project.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::GitLabClient;
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let vars = client.variables("myproject")
    ///     .list()
    ///     .await?;
    ///
    /// for var in vars {
    ///     println!("{}: {} (protected={})", var.key, var.value, var.protected);
    /// }
    /// # Ok(())
    /// # }
    /// ```
    pub async fn list(&self) -> Result<Vec<Variable>> {
        let endpoint = variables::ProjectVariables::builder()
            .project(&self.project)
            .build()
            .map_err(|e| {
                GitLabError::api(format!("Failed to build list variables request: {}", e))
            })?;

        endpoint
            .query(self.client)
            .map_err(|e| GitLabError::api(format!("Failed to list variables: {}", e)))
    }

    /// Delete a CI/CD variable.
    ///
    /// # Errors
    ///
    /// Returns an error if the variable doesn't exist or if the request fails.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::GitLabClient;
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// client.variables("myproject")
    ///     .delete("OLD_API_KEY")
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn delete(&self, key: impl AsRef<str>) -> Result<()> {
        let key = key.as_ref();

        let endpoint = variables::DeleteProjectVariable::builder()
            .project(&self.project)
            .key(key)
            .build()
            .map_err(|e| {
                GitLabError::api(format!("Failed to build delete variable request: {}", e))
            })?;

        // Delete endpoints don't return a value, use ignore() method
        gitlab::api::ignore(endpoint)
            .query(self.client)
            .map_err(|e| match e {
                ApiError::GitlabService { status, .. } if status.as_u16() == 404 => {
                    GitLabError::not_found("Variable", key)
                }
                _ => GitLabError::api(format!("Failed to delete variable '{}': {}", key, e)),
            })?;

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_variable_builder() {
        fn _compile_test(client: &Gitlab) {
            let _builder = VariableBuilder::new(client, "project");
        }
    }
}