pub mod validation;
pub mod wrapper;
use crate::Result;
use crate::validation::ViolationType;
use std::env;
use std::path::Path;
pub struct CargoInterceptor {
enforce_dogfooding: bool,
bypass_style: bool,
force_bypass: bool,
}
impl Default for CargoInterceptor {
fn default() -> Self {
Self::new()
}
}
impl CargoInterceptor {
pub fn new() -> Self {
let bypass_style = env::var("FERROUS_FORGE_BYPASS")
.unwrap_or_default()
.eq_ignore_ascii_case("true");
let force_bypass = env::var("FERROUS_FORGE_FORCE_BYPASS")
.unwrap_or_default()
.eq_ignore_ascii_case("true");
Self {
enforce_dogfooding: true,
bypass_style,
force_bypass,
}
}
pub fn with_dogfooding(enforce_dogfooding: bool) -> Self {
let mut interceptor = Self::new();
interceptor.enforce_dogfooding = enforce_dogfooding;
interceptor
}
}
pub async fn intercept_publish_command(project_path: &Path) -> Result<()> {
let interceptor = CargoInterceptor::new();
if interceptor.force_bypass {
eprintln!(
"\n⚠️ FERROUS FORGE FORCE BYPASSED — FERROUS_FORGE_FORCE_BYPASS=true\n\
All validation skipped. This should NEVER happen in production.\n"
);
return Ok(());
}
if interceptor.bypass_style {
tracing::warn!(
"FERROUS_FORGE_BYPASS enabled — style checks skipped, locked settings still enforced"
);
}
tracing::info!("Intercepting cargo publish — running validation");
validation::pre_publish_validation(project_path).await?;
validation::version_consistency_check(project_path)?;
if interceptor.enforce_dogfooding {
validation::enforce_dogfooding(project_path).await?;
}
tracing::info!("Pre-publish validation passed");
Ok(())
}
pub async fn intercept_dev_command(project_path: &Path) -> Result<()> {
let interceptor = CargoInterceptor::new();
if interceptor.force_bypass {
eprintln!(
"\n⚠️ FERROUS FORGE FORCE BYPASSED — FERROUS_FORGE_FORCE_BYPASS=true\n\
All validation skipped. This should NEVER happen in production.\n"
);
return Ok(());
}
let locked_violations = validation::check_locked_settings(project_path).await?;
if !locked_violations.is_empty() {
eprintln!("\n❌ FERROUS FORGE — Locked Setting Violations Detected\n");
for v in &locked_violations {
eprintln!("{}\n", v.message);
}
return Err(crate::Error::validation(
"Locked setting violations must be resolved before building. \
See messages above. DO NOT change locked values — escalate to human.",
));
}
if !interceptor.bypass_style {
let style_violations = validation::check_style_violations(project_path).await?;
if !style_violations.is_empty() {
eprintln!(
"\n⚠️ Ferrous Forge style warnings ({} violations):",
style_violations.len()
);
for v in style_violations.iter().take(5) {
eprintln!(
" {:?}: {}:{} — {}",
v.violation_type,
v.file.display(),
v.line,
v.message.lines().next().unwrap_or("")
);
}
if style_violations.len() > 5 {
eprintln!(
" ... and {} more (run 'ferrous-forge validate' \
for full list)",
style_violations.len() - 5
);
}
eprintln!(" (These will block 'cargo publish'. Fix before publishing.)");
eprintln!(" (Set FERROUS_FORGE_BYPASS=true to suppress these warnings.)\n");
}
} else {
tracing::info!(
"FERROUS_FORGE_BYPASS — style warnings suppressed (locked settings still checked)"
);
}
Ok(())
}
pub fn has_locked_violations(violations: &[crate::validation::Violation]) -> bool {
violations.iter().any(|v| {
matches!(
v.violation_type,
ViolationType::WrongEdition
| ViolationType::OldRustVersion
| ViolationType::LockedSetting
)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cargo_interceptor_creation() {
let interceptor = CargoInterceptor::new();
assert!(interceptor.enforce_dogfooding);
}
}