use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use std::time::Duration;
pub mod bypass;
pub mod checks;
pub mod config;
pub mod execution;
pub mod pipeline;
pub mod report;
pub use config::SafetyConfig;
pub use pipeline::SafetyPipeline;
pub use report::{CheckResult, SafetyReport};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PipelineStage {
PreCommit,
PrePush,
Publish,
}
impl PipelineStage {
pub fn name(&self) -> &'static str {
match self {
Self::PreCommit => "pre-commit",
Self::PrePush => "pre-push",
Self::Publish => "publish",
}
}
pub fn display_name(&self) -> &'static str {
match self {
Self::PreCommit => "Pre-Commit",
Self::PrePush => "Pre-Push",
Self::Publish => "Publish",
}
}
pub fn default_timeout(&self) -> Duration {
match self {
Self::PreCommit => Duration::from_secs(300), Self::PrePush => Duration::from_secs(600), Self::Publish => Duration::from_secs(900), }
}
}
impl std::str::FromStr for PipelineStage {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
match s.to_lowercase().as_str() {
"pre-commit" | "precommit" | "commit" => Ok(Self::PreCommit),
"pre-push" | "prepush" | "push" => Ok(Self::PrePush),
"publish" | "pub" => Ok(Self::Publish),
_ => Err(Error::parse(format!("Unknown pipeline stage: {}", s))),
}
}
}
impl std::fmt::Display for PipelineStage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckType {
Format,
Clippy,
Build,
Test,
Audit,
Doc,
PublishDryRun,
Standards,
DocCoverage,
License,
Semver,
}
impl CheckType {
pub fn name(&self) -> &'static str {
match self {
Self::Format => "format",
Self::Clippy => "clippy",
Self::Build => "build",
Self::Test => "test",
Self::Audit => "audit",
Self::Doc => "doc",
Self::PublishDryRun => "publish-dry-run",
Self::Standards => "standards",
Self::DocCoverage => "doc-coverage",
Self::License => "license",
Self::Semver => "semver",
}
}
pub fn display_name(&self) -> &'static str {
match self {
Self::Format => "Format Check",
Self::Clippy => "Clippy Check",
Self::Build => "Build Check",
Self::Test => "Test Check",
Self::Audit => "Security Audit",
Self::Doc => "Documentation Build",
Self::PublishDryRun => "Publish Dry Run",
Self::Standards => "Standards Check",
Self::DocCoverage => "Documentation Coverage",
Self::License => "License Check",
Self::Semver => "Semver Check",
}
}
pub fn for_stage(stage: PipelineStage) -> Vec<Self> {
match stage {
PipelineStage::PreCommit => Self::pre_commit_checks(),
PipelineStage::PrePush => Self::pre_push_checks(),
PipelineStage::Publish => Self::publish_checks(),
}
}
fn pre_commit_checks() -> Vec<Self> {
vec![Self::Format, Self::Clippy, Self::Build, Self::Standards]
}
fn pre_push_checks() -> Vec<Self> {
vec![
Self::Format,
Self::Clippy,
Self::Build,
Self::Standards,
Self::Test,
Self::Audit,
Self::Doc,
]
}
fn publish_checks() -> Vec<Self> {
vec![
Self::Format,
Self::Clippy,
Self::Build,
Self::Standards,
Self::Test,
Self::Audit,
Self::Doc,
Self::PublishDryRun,
Self::DocCoverage,
Self::License,
Self::Semver,
]
}
}
#[derive(Debug, Clone)]
pub enum SafetyResult {
Passed,
Blocked {
failures: Vec<String>,
suggestions: Vec<String>,
},
Bypassed {
reason: String,
user: String,
},
}
impl SafetyResult {
pub fn is_allowed(&self) -> bool {
matches!(self, Self::Passed | Self::Bypassed { .. })
}
pub fn message(&self) -> String {
match self {
Self::Passed => "🎉 All safety checks passed! Operation allowed.".to_string(),
Self::Blocked {
failures,
suggestions,
} => {
let mut msg = "🚨 Safety checks FAILED - operation blocked!\n\n".to_string();
if !failures.is_empty() {
msg.push_str("Failures:\n");
for failure in failures {
msg.push_str(&format!(" • {}\n", failure));
}
}
if !suggestions.is_empty() {
msg.push_str("\nSuggestions:\n");
for suggestion in suggestions {
msg.push_str(&format!(" • {}\n", suggestion));
}
}
msg
}
Self::Bypassed { reason, user } => {
format!(
"⚠️ Safety checks bypassed by {} - reason: {}",
user, reason
)
}
}
}
}
#[cfg(test)]
mod tests;