use crate::cli::{CheckOpts, OutputFormat};
use crate::error::{CommandExitCode, Result};
use command_group::CommandGroup;
use serde_json::json;
use std::path::PathBuf;
pub struct PreCheckService {
workspace_path: String,
opts: CheckOpts,
}
impl PreCheckService {
pub const fn new(workspace_path: String, opts: CheckOpts) -> Self {
Self {
workspace_path,
opts,
}
}
pub fn execute(&self, _format: OutputFormat) -> Result<CommandExitCode> {
let start_time = std::time::Instant::now();
let checks_to_run = self.get_checks_to_run();
let (results, all_passed) = self.run_all_checks(&checks_to_run)?;
let passed_count = results.iter().filter(|r| r["passed"] == true).count();
let failed_count = results.len() - passed_count;
let response = json!({
"success": all_passed,
"command": "check",
"result": {
"checks": results,
"summary": {
"total": results.len(),
"passed": passed_count,
"failed": failed_count,
}
},
"metrics": {
"execution_time_ms": start_time.elapsed().as_millis(),
}
});
println!("{}", serde_json::to_string_pretty(&response).unwrap());
if all_passed {
Ok(CommandExitCode::Success)
} else {
Ok(CommandExitCode::CheckFailed)
}
}
fn get_checks_to_run(&self) -> Vec<String> {
self.opts.checks.as_ref().map_or_else(
|| vec!["test".to_string(), "clippy".to_string(), "fmt".to_string()],
|s| s.split(',').map(|s| s.trim().to_string()).collect(),
)
}
fn run_all_checks(&self, checks_to_run: &[String]) -> Result<(Vec<serde_json::Value>, bool)> {
let mut results = Vec::new();
let mut all_passed = true;
for check in checks_to_run {
let result = self.run_check(check);
let passed = result.is_ok();
let message = result
.as_ref()
.map_or_else(std::string::ToString::to_string, |()| "Passed".to_string());
if !passed && self.opts.fail_fast {
let response = vec![json!({
"name": check,
"passed": false,
"message": message,
})];
return Ok((response, false));
}
results.push(json!({
"name": check,
"passed": passed,
"message": message,
}));
if !passed {
all_passed = false;
}
}
Ok((results, all_passed))
}
fn run_check(&self, check: &str) -> Result<()> {
match check {
"test" => {
self.run_command(&["cargo", "test", "--all-features"])?;
Ok(())
}
"clippy" => {
self.run_command(&[
"cargo",
"clippy",
"--all-targets",
"--all-features",
"--",
"-D",
"warnings",
])?;
Ok(())
}
"fmt" => {
self.run_command(&["cargo", "fmt", "--check"])?;
Ok(())
}
"doc" => {
self.run_command(&["cargo", "doc", "--no-deps", "--all-features"])?;
Ok(())
}
"build" => {
self.run_command(&["cargo", "build", "--all-features"])?;
Ok(())
}
_ => Err(crate::error::Error::Config(format!(
"Unknown check: {check}"
))),
}
}
fn run_command(&self, args: &[&str]) -> Result<std::process::ExitStatus> {
let output = std::process::Command::new(args[0])
.args(&args[1..])
.current_dir(PathBuf::from(&self.workspace_path))
.group_spawn()
.map_err(|e| crate::error::Error::Io(format!("Failed to spawn {args:?}: {e}")))?
.wait()
.map_err(|e| crate::error::Error::Io(format!("Failed to wait for {args:?}: {e}")))?;
if output.success() {
Ok(output)
} else {
Err(crate::error::Error::CheckFailed(format!(
"Command {:?} failed with exit code {:?}",
args,
output.code()
)))
}
}
}