use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
use std::process::Command;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Tier {
#[serde(rename = "tier1")]
Tier1 = 1,
#[serde(rename = "tier2")]
Tier2 = 2,
#[serde(rename = "tier3")]
Tier3 = 3,
}
impl From<u8> for Tier {
fn from(value: u8) -> Self {
match value {
1 => Tier::Tier1,
2 => Tier::Tier2,
_ => Tier::Tier3,
}
}
}
impl std::fmt::Display for Tier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Tier::Tier1 => write!(f, "Tier 1 (ON-SAVE)"),
Tier::Tier2 => write!(f, "Tier 2 (ON-COMMIT)"),
Tier::Tier3 => write!(f, "Tier 3 (NIGHTLY)"),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GateConfig {
#[serde(default)]
pub metadata: MetadataConfig,
#[serde(default)]
pub gates: GatesConfig,
#[serde(default)]
pub tiers: TiersConfig,
#[serde(default)]
pub risk_based: RiskBasedConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetadataConfig {
#[serde(default = "default_version")]
pub version: String,
#[serde(default = "default_tool")]
pub tool: String,
}
fn default_version() -> String {
"1.0.0".to_string()
}
fn default_tool() -> String {
"bashrs".to_string()
}
impl Default for MetadataConfig {
fn default() -> Self {
Self {
version: default_version(),
tool: default_tool(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GatesConfig {
#[serde(default = "default_true")]
pub run_clippy: bool,
#[serde(default = "default_true")]
pub clippy_strict: bool,
#[serde(default = "default_true")]
pub run_tests: bool,
#[serde(default = "default_timeout")]
pub test_timeout: u64,
#[serde(default = "default_true")]
pub check_coverage: bool,
#[serde(default = "default_coverage")]
pub min_coverage: f64,
#[serde(default = "default_true")]
pub check_complexity: bool,
#[serde(default = "default_complexity")]
pub max_complexity: u32,
#[serde(default)]
pub satd: SatdConfig,
#[serde(default)]
pub mutation: MutationConfig,
#[serde(default)]
pub security: SecurityConfig,
}
fn default_true() -> bool {
true
}
fn default_timeout() -> u64 {
300
}
fn default_coverage() -> f64 {
85.0
}
fn default_complexity() -> u32 {
10
}
impl Default for GatesConfig {
fn default() -> Self {
Self {
run_clippy: true,
clippy_strict: true,
run_tests: true,
test_timeout: 300,
check_coverage: true,
min_coverage: 85.0,
check_complexity: true,
max_complexity: 10,
satd: SatdConfig::default(),
mutation: MutationConfig::default(),
security: SecurityConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SatdConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default)]
pub max_count: usize,
#[serde(default = "default_satd_patterns")]
pub patterns: Vec<String>,
#[serde(default)]
pub require_issue_links: bool,
#[serde(default = "default_true")]
pub fail_on_violation: bool,
}
fn default_satd_patterns() -> Vec<String> {
vec![
"TODO".to_string(),
"FIXME".to_string(),
"HACK".to_string(),
"XXX".to_string(),
]
}
impl Default for SatdConfig {
fn default() -> Self {
Self {
enabled: true,
max_count: 0,
patterns: default_satd_patterns(),
require_issue_links: true,
fail_on_violation: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MutationConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_mutation_score")]
pub min_score: f64,
#[serde(default = "default_mutation_tool")]
pub tool: String,
#[serde(default = "default_mutation_strategy")]
pub strategy: String,
}
fn default_mutation_score() -> f64 {
85.0
}
fn default_mutation_tool() -> String {
"cargo-mutants".to_string()
}
fn default_mutation_strategy() -> String {
"incremental".to_string()
}
impl Default for MutationConfig {
fn default() -> Self {
Self {
enabled: false,
min_score: 85.0,
tool: default_mutation_tool(),
strategy: default_mutation_strategy(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
#[serde(default = "default_true")]
pub enabled: bool,
#[serde(default = "default_audit_vulnerabilities")]
pub audit_vulnerabilities: String,
#[serde(default = "default_audit_unmaintained")]
pub audit_unmaintained: String,
#[serde(default)]
pub max_unsafe_blocks: usize,
#[serde(default = "default_true")]
pub fail_on_violation: bool,
}
fn default_audit_vulnerabilities() -> String {
"deny".to_string()
}
fn default_audit_unmaintained() -> String {
"warn".to_string()
}
impl Default for SecurityConfig {
fn default() -> Self {
Self {
enabled: true,
audit_vulnerabilities: default_audit_vulnerabilities(),
audit_unmaintained: default_audit_unmaintained(),
max_unsafe_blocks: 0,
fail_on_violation: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TiersConfig {
#[serde(default = "default_tier1_gates")]
pub tier1_gates: Vec<String>,
#[serde(default = "default_tier2_gates")]
pub tier2_gates: Vec<String>,
#[serde(default = "default_tier3_gates")]
pub tier3_gates: Vec<String>,
}
fn default_tier1_gates() -> Vec<String> {
vec!["clippy".to_string(), "complexity".to_string()]
}
fn default_tier2_gates() -> Vec<String> {
vec![
"clippy".to_string(),
"tests".to_string(),
"coverage".to_string(),
"satd".to_string(),
]
}
fn default_tier3_gates() -> Vec<String> {
vec![
"clippy".to_string(),
"tests".to_string(),
"coverage".to_string(),
"satd".to_string(),
"mutation".to_string(),
"security".to_string(),
]
}
impl Default for TiersConfig {
fn default() -> Self {
Self {
tier1_gates: default_tier1_gates(),
tier2_gates: default_tier2_gates(),
tier3_gates: default_tier3_gates(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RiskBasedConfig {
#[serde(default = "default_very_high_risk_target")]
pub very_high_risk_mutation_target: f64,
#[serde(default)]
pub very_high_risk_components: Vec<String>,
#[serde(default = "default_high_risk_target")]
pub high_risk_mutation_target: f64,
#[serde(default)]
pub high_risk_components: Vec<String>,
}
include!("gates_default.rs");