use std::path::PathBuf;
use std::process::ExitCode;
use std::time::Instant;
use clap::ColorChoice;
use clap::Parser;
use mago_database::DatabaseReader;
use mago_database::file::FileType;
use mago_guard::settings::GuardMode;
use mago_prelude::Prelude;
use crate::commands::args::baseline_reporting::BaselineReportingArgs;
use crate::commands::stdin_input;
use crate::config::Configuration;
use crate::consts::PRELUDE_BYTES;
use crate::error::Error;
use crate::utils::create_orchestrator;
#[derive(Parser, Debug)]
#[command(name = "guard")]
pub struct GuardCommand {
#[arg()]
pub path: Vec<PathBuf>,
#[arg(long, default_value_t = false)]
pub no_stubs: bool,
#[arg(long, conflicts_with = "perimeter")]
pub structural: bool,
#[arg(long, conflicts_with = "structural")]
pub perimeter: bool,
#[arg(long, default_value_t = false)]
pub stdin_input: bool,
#[clap(flatten)]
pub baseline_reporting: BaselineReportingArgs,
}
impl GuardCommand {
pub fn execute(self, mut configuration: Configuration, color_choice: ColorChoice) -> Result<ExitCode, Error> {
let trace_enabled = tracing::enabled!(tracing::Level::TRACE);
let command_start = trace_enabled.then(Instant::now);
let prelude_start = trace_enabled.then(Instant::now);
let Prelude { database, metadata, .. } = if self.no_stubs {
Prelude::default()
} else {
Prelude::decode(PRELUDE_BYTES).expect("Failed to decode embedded prelude")
};
let prelude_duration = prelude_start.map(|s| s.elapsed());
let cli_mode = if self.structural {
Some(GuardMode::Structural)
} else if self.perimeter {
Some(GuardMode::Perimeter)
} else {
None
};
if let Some(mode) = cli_mode {
let config_mode = configuration.guard.settings.mode;
if config_mode != GuardMode::Default && config_mode != mode {
tracing::info!(
"Overriding guard mode from configuration `{}` with CLI flag `{}`",
config_mode.as_str(),
mode.as_str()
);
} else if config_mode == mode {
tracing::warn!(
"CLI flag --{} is redundant: same mode already set in configuration",
if mode == GuardMode::Structural { "structural" } else { "perimeter" }
);
}
configuration.guard.settings.mode = mode;
}
let editor_url = configuration.editor_url.take();
let orchestrator_init_start = trace_enabled.then(Instant::now);
let mut orchestrator = create_orchestrator(&configuration, color_choice, false, true, false);
orchestrator.add_exclude_patterns(configuration.guard.excludes.iter());
let stdin_override = stdin_input::resolve_stdin_override(
self.stdin_input,
&self.path,
&configuration.source.workspace,
&mut orchestrator,
)?;
if !self.stdin_input && !self.path.is_empty() {
stdin_input::set_source_paths_from_paths(&mut orchestrator, &self.path);
}
let orchestrator_init_duration = orchestrator_init_start.map(|s| s.elapsed());
let load_database_start = trace_enabled.then(Instant::now);
let mut database =
orchestrator.load_database(&configuration.source.workspace, true, Some(database), stdin_override)?;
let load_database_duration = load_database_start.map(|s| s.elapsed());
if !database.files().any(|f| f.file_type == FileType::Host) {
tracing::warn!("No files found to check with guard.");
return Ok(ExitCode::SUCCESS);
}
let guard_run_start = trace_enabled.then(Instant::now);
let service = orchestrator.get_guard_service(database.read_only(), metadata);
let result = service.run()?;
let guard_run_duration = guard_run_start.map(|s| s.elapsed());
if result.missing_perimeter_configuration {
tracing::warn!("Perimeter guard checks were skipped due to missing configuration.");
tracing::warn!("Please define perimeter rules in your mago.toml to enable these checks.");
}
if result.missing_structural_configuration {
tracing::warn!("Structural guard checks were skipped based on the current configuration.");
tracing::warn!("Please review your mago.toml guard settings to enable structural checks.");
}
let report_start = trace_enabled.then(Instant::now);
let baseline = configuration.guard.baseline.as_deref();
let baseline_variant = configuration.guard.baseline_variant;
let processor = self.baseline_reporting.get_processor(
color_choice,
baseline,
baseline_variant,
editor_url,
configuration.guard.minimum_fail_level,
);
let (exit_code, _) = processor.process_issues(&orchestrator, &mut database, result.issues)?;
let report_duration = report_start.map(|s| s.elapsed());
let drop_database_start = trace_enabled.then(Instant::now);
drop(database);
let drop_database_duration = drop_database_start.map(|s| s.elapsed());
let drop_orchestrator_start = trace_enabled.then(Instant::now);
drop(orchestrator);
let drop_orchestrator_duration = drop_orchestrator_start.map(|s| s.elapsed());
if let Some(start) = command_start {
tracing::trace!("Prelude decoded in {:?}.", prelude_duration.unwrap_or_default());
tracing::trace!("Orchestrator initialized in {:?}.", orchestrator_init_duration.unwrap_or_default());
tracing::trace!("Database loaded in {:?}.", load_database_duration.unwrap_or_default());
tracing::trace!("Guard service ran in {:?}.", guard_run_duration.unwrap_or_default());
tracing::trace!("Issues filtered and reported in {:?}.", report_duration.unwrap_or_default());
tracing::trace!("Database dropped in {:?}.", drop_database_duration.unwrap_or_default());
tracing::trace!("Orchestrator dropped in {:?}.", drop_orchestrator_duration.unwrap_or_default());
tracing::trace!("Guard command finished in {:?}.", start.elapsed());
}
Ok(exit_code)
}
}