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 API operations.
//!
//! This module provides operations at the project level, including
//! creating pipelines, managing project settings, and more.

use crate::error::{GitLabError, Result};
use crate::models::Pipeline;
use gitlab::Gitlab;
use gitlab::api::{
    Query,
    projects::pipelines::{self, PipelineVariable},
};
use std::collections::HashMap;

/// Builder for project-level operations.
///
/// Provides operations that work at the project scope rather than
/// on specific resources.
///
/// # Examples
///
/// ```no_run
/// use lmrc_gitlab::GitLabClient;
///
/// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
/// let client = GitLabClient::new("https://gitlab.com", "token")?;
///
/// // Trigger a new pipeline
/// let pipeline = client.project("myorg/myproject")
///     .create_pipeline()
///     .ref_name("main")
///     .trigger()
///     .await?;
/// # Ok(())
/// # }
/// ```
pub struct ProjectBuilder<'a> {
    client: &'a Gitlab,
    project: String,
}

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

    /// Create a builder for triggering a new pipeline.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::GitLabClient;
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let pipeline = client.project("myorg/myproject")
    ///     .create_pipeline()
    ///     .ref_name("main")
    ///     .trigger()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn create_pipeline(self) -> CreatePipelineBuilder<'a> {
        CreatePipelineBuilder::new(self.client, self.project)
    }
}

/// Builder for creating/triggering a new pipeline.
///
/// # Examples
///
/// ```no_run
/// use lmrc_gitlab::GitLabClient;
/// use std::collections::HashMap;
///
/// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
/// let client = GitLabClient::new("https://gitlab.com", "token")?;
///
/// // Trigger pipeline with variables
/// let mut vars = HashMap::new();
/// vars.insert("DEPLOY_ENV".to_string(), "staging".to_string());
///
/// let pipeline = client.project("myorg/myproject")
///     .create_pipeline()
///     .ref_name("main")
///     .variables(vars)
///     .trigger()
///     .await?;
///
/// println!("Created pipeline #{}", pipeline.id);
/// # Ok(())
/// # }
/// ```
pub struct CreatePipelineBuilder<'a> {
    client: &'a Gitlab,
    project: String,
    ref_name: Option<String>,
    variables: HashMap<String, String>,
}

impl<'a> CreatePipelineBuilder<'a> {
    /// Creates a new pipeline creation builder.
    pub(crate) fn new(client: &'a Gitlab, project: impl Into<String>) -> Self {
        Self {
            client,
            project: project.into(),
            ref_name: None,
            variables: HashMap::new(),
        }
    }

    /// Set the git reference (branch or tag) to run the pipeline on.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::GitLabClient;
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let pipeline = client.project("myorg/myproject")
    ///     .create_pipeline()
    ///     .ref_name("develop")
    ///     .trigger()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn ref_name(mut self, ref_name: impl Into<String>) -> Self {
        self.ref_name = Some(ref_name.into());
        self
    }

    /// Set CI/CD variables for the pipeline.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::GitLabClient;
    /// # use std::collections::HashMap;
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let mut vars = HashMap::new();
    /// vars.insert("DEPLOY_TARGET".to_string(), "production".to_string());
    ///
    /// let pipeline = client.project("myorg/myproject")
    ///     .create_pipeline()
    ///     .ref_name("main")
    ///     .variables(vars)
    ///     .trigger()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn variables(mut self, variables: HashMap<String, String>) -> Self {
        self.variables = variables;
        self
    }

    /// Add a single CI/CD variable for the pipeline.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::GitLabClient;
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let pipeline = client.project("myorg/myproject")
    ///     .create_pipeline()
    ///     .ref_name("main")
    ///     .variable("ENVIRONMENT", "staging")
    ///     .variable("DEBUG", "true")
    ///     .trigger()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn variable(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
        self.variables.insert(key.into(), value.into());
        self
    }

    /// Trigger the pipeline.
    ///
    /// # Errors
    ///
    /// Returns [`GitLabError::InvalidInput`] if ref_name is not set.
    /// Returns an error if the pipeline cannot be created.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::GitLabClient;
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let pipeline = client.project("myorg/myproject")
    ///     .create_pipeline()
    ///     .ref_name("main")
    ///     .trigger()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn trigger(self) -> Result<Pipeline> {
        let ref_name = self
            .ref_name
            .ok_or_else(|| GitLabError::invalid_input("ref", "ref_name is required"))?;

        let mut endpoint_builder = pipelines::CreatePipeline::builder();
        endpoint_builder.project(&self.project).ref_(&ref_name);

        // Add variables if provided
        for (key, value) in self.variables {
            let var = PipelineVariable::builder()
                .key(key)
                .value(value)
                .build()
                .map_err(|e| {
                    GitLabError::api(format!("Failed to build pipeline variable: {}", e))
                })?;
            endpoint_builder.variable(var);
        }

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

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

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

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

    #[test]
    fn test_create_pipeline_builder() {
        fn _compile_test(client: &Gitlab) {
            let _builder = CreatePipelineBuilder::new(client, "project")
                .ref_name("main")
                .variable("KEY", "value");
        }
    }
}