Skip to main content

ferrous_forge/safety/
execution.rs

1//! Safety pipeline execution logic
2//!
3//! This module contains the core execution logic for safety checks,
4//! including progress display and parallel/sequential execution.
5
6use super::{
7    CheckType, PipelineStage, checks,
8    config::StageConfig,
9    report::{CheckResult, SafetyReport},
10};
11use crate::Result;
12use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
13use std::path::Path;
14use std::time::Duration;
15
16/// Progress display and execution coordinator
17pub struct ExecutionManager {
18    /// Whether to show progress indicators
19    show_progress: bool,
20    /// Whether to run checks in parallel
21    parallel_checks: bool,
22}
23
24impl ExecutionManager {
25    /// Create a new execution manager
26    pub fn new(show_progress: bool, parallel_checks: bool) -> Self {
27        Self {
28            show_progress,
29            parallel_checks,
30        }
31    }
32
33    /// Setup progress indicators and display header
34    pub fn setup_progress_display(&self, stage: PipelineStage) -> Option<MultiProgress> {
35        let multi_progress = if self.show_progress {
36            Some(MultiProgress::new())
37        } else {
38            None
39        };
40
41        println!(
42            "🛡️  Ferrous Forge Safety Pipeline - {}",
43            stage.display_name()
44        );
45        println!("{}", "=".repeat(50));
46
47        multi_progress
48    }
49
50    /// Execute checks for the stage
51    ///
52    /// # Errors
53    ///
54    /// Returns an error if any individual check fails to execute.
55    pub async fn execute_stage_checks(
56        &self,
57        stage_config: &StageConfig,
58        report: &mut SafetyReport,
59        multi_progress: Option<&MultiProgress>,
60        project_path: &Path,
61    ) -> Result<()> {
62        let checks = &stage_config.checks;
63
64        if self.parallel_checks && checks.len() > 1 {
65            self.run_checks_parallel(checks, report, multi_progress, project_path)
66                .await
67        } else {
68            self.run_checks_sequential(checks, report, multi_progress, project_path)
69                .await
70        }
71    }
72
73    /// Run checks sequentially with progress indicators
74    async fn run_checks_sequential(
75        &self,
76        checks: &[CheckType],
77        report: &mut SafetyReport,
78        multi_progress: Option<&MultiProgress>,
79        project_path: &Path,
80    ) -> Result<()> {
81        for check_type in checks {
82            let pb = if let Some(mp) = multi_progress {
83                let pb = mp.add(ProgressBar::new_spinner());
84                pb.set_style(
85                    ProgressStyle::default_spinner()
86                        .template("{spinner:.green} {msg}")
87                        .unwrap_or_else(|_| ProgressStyle::default_spinner()),
88                );
89                pb.set_message(format!("Running {}...", check_type.display_name()));
90                pb.enable_steady_tick(Duration::from_millis(100));
91                Some(pb)
92            } else {
93                None
94            };
95
96            let check_result = execute_check(*check_type, project_path).await?;
97
98            if let Some(pb) = pb {
99                pb.finish_with_message(format!(
100                    "{} {} ({:.2}s)",
101                    check_result.status_emoji(),
102                    check_type.display_name(),
103                    check_result.duration.as_secs_f64()
104                ));
105            } else {
106                println!(
107                    "  {} {} ({:.2}s)",
108                    check_result.status_emoji(),
109                    check_type.display_name(),
110                    check_result.duration.as_secs_f64()
111                );
112            }
113
114            report.add_check(check_result);
115        }
116
117        Ok(())
118    }
119
120    /// Run checks in parallel for better performance
121    async fn run_checks_parallel(
122        &self,
123        checks: &[CheckType],
124        report: &mut SafetyReport,
125        _multi_progress: Option<&MultiProgress>,
126        project_path: &Path,
127    ) -> Result<()> {
128        // For now, implement as sequential until we add proper parallel execution
129        // Parallel execution requires careful handling of stdout/stderr
130        self.run_checks_sequential(checks, report, _multi_progress, project_path)
131            .await
132    }
133}
134
135/// Execute a specific check type
136///
137/// # Errors
138///
139/// Returns an error if the underlying check command fails to run.
140pub async fn execute_check(check_type: CheckType, project_path: &Path) -> Result<CheckResult> {
141    match check_type {
142        CheckType::Format => checks::format::run(project_path).await,
143        CheckType::Clippy => checks::clippy::run(project_path).await,
144        CheckType::Build => checks::build::run(project_path).await,
145        CheckType::Test => checks::test::run(project_path).await,
146        CheckType::Audit => checks::audit::run(project_path).await,
147        CheckType::Doc => checks::doc::run(project_path).await,
148        CheckType::PublishDryRun => checks::publish::run(project_path).await,
149        CheckType::Standards => checks::standards::run(project_path).await,
150        CheckType::DocCoverage => checks::doc::coverage_check(project_path).await,
151        CheckType::License => checks::license::run(project_path).await,
152        CheckType::Semver => checks::semver::run(project_path).await,
153    }
154}
155
156/// Handle check result and convert errors to check results
157///
158/// # Errors
159///
160/// This function converts check errors into [`CheckResult`] values and
161/// does not itself return errors.
162pub fn handle_check_result(
163    check_result: Result<CheckResult>,
164    check_type: CheckType,
165) -> Result<CheckResult> {
166    match check_result {
167        Ok(result) => Ok(result),
168        Err(e) => {
169            let mut result = CheckResult::new(check_type);
170            result.add_error(format!("Check failed: {}", e));
171            result.add_suggestion("Check the error message above for details");
172            Ok(result)
173        }
174    }
175}
176
177/// Get the appropriate stage for a check type
178pub fn get_stage_for_check(check_type: CheckType) -> PipelineStage {
179    // Find the first stage that includes this check
180    for stage in [
181        PipelineStage::PreCommit,
182        PipelineStage::PrePush,
183        PipelineStage::Publish,
184    ] {
185        if CheckType::for_stage(stage).contains(&check_type) {
186            return stage;
187        }
188    }
189    PipelineStage::PreCommit // Default fallback
190}