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
//! Pipeline API operations with builder pattern.

use crate::error::{GitLabError, Result};
use crate::models::{Pipeline, PipelineBasic, PipelineStatus};
use gitlab::Gitlab;
use gitlab::api::{Query, projects::pipelines};

/// Builder for pipeline list operations.
///
/// Provides a fluent API for querying pipelines with various filters.
///
/// # Examples
///
/// ```no_run
/// use lmrc_gitlab::{GitLabClient, models::PipelineStatus};
///
/// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
/// let client = GitLabClient::new("https://gitlab.com", "token")?;
///
/// // List failed pipelines on main branch
/// let pipelines = client.pipelines("myorg/myproject")
///     .status(PipelineStatus::Failed)
///     .ref_name("main")
///     .limit(10)
///     .list()
///     .await?;
/// # Ok(())
/// # }
/// ```
pub struct PipelineListBuilder<'a> {
    client: &'a Gitlab,
    project: String,
    status: Option<PipelineStatus>,
    ref_name: Option<String>,
    limit: Option<usize>,
}

impl<'a> PipelineListBuilder<'a> {
    /// Creates a new pipeline list builder.
    pub(crate) fn new(client: &'a Gitlab, project: impl Into<String>) -> Self {
        Self {
            client,
            project: project.into(),
            status: None,
            ref_name: None,
            limit: None,
        }
    }

    /// Filter pipelines by status.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::{GitLabClient, models::PipelineStatus};
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let pipelines = client.pipelines("myorg/myproject")
    ///     .status(PipelineStatus::Failed)
    ///     .list()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn status(mut self, status: PipelineStatus) -> Self {
        self.status = Some(status);
        self
    }

    /// Filter pipelines by git reference (branch or tag).
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::GitLabClient;
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let pipelines = client.pipelines("myorg/myproject")
    ///     .ref_name("main")
    ///     .list()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn ref_name(mut self, ref_name: impl Into<String>) -> Self {
        self.ref_name = Some(ref_name.into());
        self
    }

    /// Limit the number of pipelines returned.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use lmrc_gitlab::GitLabClient;
    /// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
    /// # let client = GitLabClient::new("https://gitlab.com", "token")?;
    /// let pipelines = client.pipelines("myorg/myproject")
    ///     .limit(5)
    ///     .list()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn limit(mut self, limit: usize) -> Self {
        self.limit = Some(limit);
        self
    }

    /// Execute the query and return the list of pipelines.
    ///
    /// # Errors
    ///
    /// Returns an error if the API request fails or if the project is not found.
    pub async fn list(self) -> Result<Vec<PipelineBasic>> {
        let endpoint = pipelines::Pipelines::builder()
            .project(&self.project)
            .build()
            .map_err(|e| GitLabError::api(format!("Failed to build pipeline endpoint: {}", e)))?;

        let mut pipelines: Vec<PipelineBasic> = endpoint
            .query(self.client)
            .map_err(|e| GitLabError::api(format!("Failed to query pipelines: {}", e)))?;

        // Apply filters
        if let Some(status) = self.status {
            pipelines.retain(|p| p.status == status);
        }

        if let Some(ref_name) = self.ref_name {
            pipelines.retain(|p| p.ref_name == ref_name);
        }

        // Apply limit
        if let Some(limit) = self.limit {
            pipelines.truncate(limit);
        }

        Ok(pipelines)
    }
}

/// Builder for single pipeline operations.
///
/// Provides operations for a specific pipeline including get, retry, and cancel.
///
/// # Examples
///
/// ```no_run
/// use lmrc_gitlab::GitLabClient;
///
/// # async fn example() -> Result<(), lmrc_gitlab::GitLabError> {
/// let client = GitLabClient::new("https://gitlab.com", "token")?;
///
/// // Get pipeline details
/// let pipeline = client.pipeline("myorg/myproject", 12345)
///     .get()
///     .await?;
///
/// println!("Pipeline status: {}", pipeline.status);
/// # Ok(())
/// # }
/// ```
pub struct PipelineBuilder<'a> {
    client: &'a Gitlab,
    project: String,
    pipeline_id: u64,
}

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

    /// Get the pipeline details.
    ///
    /// # Errors
    ///
    /// Returns [`GitLabError::NotFound`] if the pipeline doesn't exist.
    ///
    /// # 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.pipeline("myorg/myproject", 12345)
    ///     .get()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn get(self) -> Result<Pipeline> {
        let endpoint = pipelines::Pipeline::builder()
            .project(&self.project)
            .pipeline(self.pipeline_id)
            .build()
            .map_err(|e| GitLabError::api(format!("Failed to build pipeline endpoint: {}", e)))?;

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

    /// Retry the pipeline.
    ///
    /// This creates a new pipeline based on the failed jobs of the original pipeline.
    ///
    /// # Errors
    ///
    /// Returns an error if the pipeline cannot be retried (e.g., it's still running).
    ///
    /// # 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.pipeline("myorg/myproject", 12345)
    ///     .retry()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn retry(self) -> Result<Pipeline> {
        let endpoint = pipelines::RetryPipeline::builder()
            .project(&self.project)
            .pipeline(self.pipeline_id)
            .build()
            .map_err(|e| GitLabError::api(format!("Failed to build retry endpoint: {}", e)))?;

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

    /// Cancel the pipeline.
    ///
    /// Stops all running jobs in the pipeline.
    ///
    /// # Errors
    ///
    /// Returns an error if the pipeline cannot be canceled (e.g., already finished).
    ///
    /// # 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.pipeline("myorg/myproject", 12345)
    ///     .cancel()
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    pub async fn cancel(self) -> Result<Pipeline> {
        let endpoint = pipelines::CancelPipeline::builder()
            .project(&self.project)
            .pipeline(self.pipeline_id)
            .build()
            .map_err(|e| GitLabError::api(format!("Failed to build cancel endpoint: {}", e)))?;

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

/// High-level pipeline API.
///
/// This struct provides the entry point for pipeline operations.
pub struct PipelineApi;

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

    #[test]
    fn test_pipeline_list_builder() {
        // This is a compile-time test to ensure the API is ergonomic
        fn _compile_test(client: &Gitlab) {
            let _builder = PipelineListBuilder::new(client, "project")
                .status(PipelineStatus::Failed)
                .ref_name("main")
                .limit(10);
        }
    }

    #[test]
    fn test_pipeline_builder() {
        // Compile-time test for single pipeline operations
        fn _compile_test(client: &Gitlab) {
            let _builder = PipelineBuilder::new(client, "project", 123);
        }
    }
}