vsec 0.0.1

Detect secrets and in Rust codebases
Documentation
// src/scoring/thresholds.rs

use crate::models::Severity;

/// Sensitivity presets for different use cases
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SensitivityPreset {
    /// Paranoid: Report everything suspicious (threshold: 40)
    /// Use for: Security audits, pre-release reviews
    Paranoid,

    /// High: Catch most issues with some false positives (threshold: 55)
    /// Use for: CI/CD pipelines with manual review
    High,

    /// Normal: Balanced detection (threshold: 70)
    /// Use for: Regular development workflow
    Normal,

    /// Low: Only high-confidence findings (threshold: 85)
    /// Use for: Large codebases, automated blocking
    Low,

    /// Custom threshold
    Custom(i32),
}

impl SensitivityPreset {
    /// Get the threshold value for this preset
    pub fn threshold(&self) -> i32 {
        match self {
            Self::Paranoid => 40,
            Self::High => 55,
            Self::Normal => 70,
            Self::Low => 85,
            Self::Custom(t) => *t,
        }
    }

    /// Get the name of this preset
    pub fn name(&self) -> &'static str {
        match self {
            Self::Paranoid => "paranoid",
            Self::High => "high",
            Self::Low => "low",
            Self::Normal => "normal",
            Self::Custom(_) => "custom",
        }
    }

    /// Parse a preset from a string
    pub fn from_name(name: &str) -> Option<Self> {
        match name.to_lowercase().as_str() {
            "paranoid" => Some(Self::Paranoid),
            "high" => Some(Self::High),
            "normal" | "default" => Some(Self::Normal),
            "low" => Some(Self::Low),
            _ => name.parse::<i32>().ok().map(Self::Custom),
        }
    }
}

impl Default for SensitivityPreset {
    fn default() -> Self {
        Self::Normal
    }
}

/// Threshold configuration
#[derive(Debug, Clone)]
pub struct ThresholdConfig {
    /// The sensitivity preset
    pub preset: SensitivityPreset,

    /// Thresholds for severity levels
    pub critical_threshold: i32,
    pub high_threshold: i32,
    pub medium_threshold: i32,
    pub low_threshold: i32,

    /// Whether to report findings below the main threshold
    pub report_below_threshold: bool,

    /// Minimum score to even consider (quick filter)
    pub minimum_score: i32,
}

impl Default for ThresholdConfig {
    fn default() -> Self {
        Self::from_preset(SensitivityPreset::Normal)
    }
}

impl ThresholdConfig {
    /// Create config from a preset
    pub fn from_preset(preset: SensitivityPreset) -> Self {
        let base = preset.threshold();
        Self {
            preset,
            critical_threshold: base + 30,
            high_threshold: base,
            medium_threshold: base - 20,
            low_threshold: base - 40,
            report_below_threshold: false,
            minimum_score: 0,
        }
    }

    /// Determine severity from score
    pub fn severity_for_score(&self, score: i32) -> Severity {
        if score >= self.critical_threshold {
            Severity::Critical
        } else if score >= self.high_threshold {
            Severity::High
        } else if score >= self.medium_threshold {
            Severity::Medium
        } else if score >= self.low_threshold {
            Severity::Low
        } else {
            Severity::Info
        }
    }

    /// Check if a score should be reported
    pub fn should_report(&self, score: i32) -> bool {
        if self.report_below_threshold {
            score >= self.minimum_score
        } else {
            score >= self.preset.threshold()
        }
    }

    /// Get the main reporting threshold
    pub fn report_threshold(&self) -> i32 {
        self.preset.threshold()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_preset_thresholds() {
        assert_eq!(SensitivityPreset::Paranoid.threshold(), 40);
        assert_eq!(SensitivityPreset::High.threshold(), 55);
        assert_eq!(SensitivityPreset::Normal.threshold(), 70);
        assert_eq!(SensitivityPreset::Low.threshold(), 85);
        assert_eq!(SensitivityPreset::Custom(60).threshold(), 60);
    }

    #[test]
    fn test_severity_classification() {
        let config = ThresholdConfig::from_preset(SensitivityPreset::Normal);

        assert_eq!(config.severity_for_score(100), Severity::Critical);
        assert_eq!(config.severity_for_score(75), Severity::High);
        assert_eq!(config.severity_for_score(55), Severity::Medium);
        assert_eq!(config.severity_for_score(35), Severity::Low);
        assert_eq!(config.severity_for_score(20), Severity::Info);
    }

    #[test]
    fn test_preset_parsing() {
        assert_eq!(
            SensitivityPreset::from_name("paranoid"),
            Some(SensitivityPreset::Paranoid)
        );
        assert_eq!(
            SensitivityPreset::from_name("NORMAL"),
            Some(SensitivityPreset::Normal)
        );
        assert_eq!(
            SensitivityPreset::from_name("60"),
            Some(SensitivityPreset::Custom(60))
        );
        assert_eq!(SensitivityPreset::from_name("invalid"), None);
    }
}