use crate::config::defaults::{
DEFAULT_COMPLEX_FUNCTION_THRESHOLD, DEFAULT_COMPLEXITY_HIGH_THRESHOLD,
DEFAULT_COMPLEXITY_MEDIUM_THRESHOLD, DEFAULT_HUGE_FILE_LINES, DEFAULT_IMPACT_PATH_DEPTH,
DEFAULT_INSTABILITY_HUB_MIN_FAN_IN, DEFAULT_INSTABILITY_HUB_MIN_INSTABILITY_PCT,
DEFAULT_LONG_FUNCTION_LINES, DEFAULT_MAX_CONTROL_FLOW_DEPTH, DEFAULT_MAX_DIRECTORY_DEPTH,
DEFAULT_MAX_DIRECTORY_MODULES, DEFAULT_MAX_FAN_OUT, DEFAULT_MAX_FILE_BYTES,
DEFAULT_MAX_FILE_LINES, default_ignored_paths,
};
use crate::output::OutputFormat;
use crate::scan::config::ScanConfig;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct RepoPilotConfig {
pub scan: ScanSection,
pub review: ReviewSection,
pub rules: RulesSection,
pub architecture: ArchitectureSection,
pub code_quality: CodeQualitySection,
pub testing: TestingSection,
pub security: SecuritySection,
pub security_boundary: SecurityBoundarySection,
pub behavioral: BehavioralSection,
pub algorithmic: AlgorithmicSection,
pub taint: TaintSection,
pub output: OutputSection,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct RulesSection {
pub disable: Vec<String>,
pub severity_overrides: std::collections::BTreeMap<String, String>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ReviewScope {
#[default]
Changed,
Full,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub enum ReviewFailOn {
#[default]
None,
Definitely,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct ReviewSection {
pub scope: ReviewScope,
pub fail_on: ReviewFailOn,
pub impact_path_depth: usize,
}
impl Default for ReviewSection {
fn default() -> Self {
Self {
scope: ReviewScope::default(),
fail_on: ReviewFailOn::default(),
impact_path_depth: DEFAULT_IMPACT_PATH_DEPTH,
}
}
}
impl RepoPilotConfig {
pub fn to_scan_config(&self) -> ScanConfig {
let mut config =
ScanConfig::default().with_large_file_loc_threshold(self.architecture.max_file_lines);
config.ignored_paths = self.scan.ignore.clone();
config.max_file_bytes = self.scan.max_file_bytes;
config.detect_missing_tests = self.testing.detect_missing_tests;
config.detect_secret_like_names = self.security.detect_secret_like_names;
config.huge_file_loc_threshold = self.architecture.huge_file_lines;
if config.huge_file_loc_threshold <= config.large_file_loc_threshold {
config.huge_file_loc_threshold = config
.large_file_loc_threshold
.saturating_mul(3)
.max(config.large_file_loc_threshold + 1);
}
config.max_directory_modules = self.architecture.max_directory_modules;
config.max_directory_depth = self.architecture.max_directory_depth;
config.long_function_loc_threshold = self.architecture.max_function_lines;
config.max_fan_out = self.architecture.max_fan_out;
config.instability_hub_min_fan_in = self.architecture.instability_hub_min_fan_in;
config.instability_hub_min_instability_pct =
self.architecture.instability_hub_min_instability_pct;
config.complexity_medium_threshold = self.code_quality.complexity_medium_threshold;
config.complexity_high_threshold = self.code_quality.complexity_high_threshold;
config.max_control_flow_depth = self.code_quality.max_control_flow_depth;
config.complex_function_threshold = self.code_quality.complex_function_threshold;
if !self.architecture.module_mappings.is_empty() {
config.module_mappings = self.architecture.module_mappings.clone();
}
config.architecture_layers = self
.architecture
.layers
.iter()
.map(|layer| crate::scan::config::LayerSpec {
name: layer.name.clone(),
paths: layer.paths.clone(),
})
.collect();
config.package_roots = self.architecture.package_roots.clone();
self.rules.apply_to(&mut config);
config
}
}
impl RulesSection {
fn apply_to(&self, config: &mut ScanConfig) {
for rule_id in &self.disable {
if crate::rules::lookup_rule_metadata(rule_id).is_none() {
config.rule_config_problems.push(format!(
"[rules] disable lists unknown rule id `{rule_id}`; entry ignored"
));
continue;
}
config.disabled_rules.insert(rule_id.clone());
}
for (rule_id, label) in &self.severity_overrides {
if crate::rules::lookup_rule_metadata(rule_id).is_none() {
config.rule_config_problems.push(format!(
"[rules.severity_overrides] lists unknown rule id `{rule_id}`; entry ignored"
));
continue;
}
let Some(severity) = crate::findings::types::Severity::from_lowercase_label(label)
else {
config.rule_config_problems.push(format!(
"[rules.severity_overrides] `{rule_id}` has invalid severity `{label}` (expected info/low/medium/high/critical); entry ignored"
));
continue;
};
config.severity_overrides.insert(rule_id.clone(), severity);
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct ScanSection {
#[serde(default = "default_ignored_paths")]
pub ignore: Vec<String>,
pub max_file_bytes: u64,
}
impl Default for ScanSection {
fn default() -> Self {
Self {
ignore: default_ignored_paths(),
max_file_bytes: DEFAULT_MAX_FILE_BYTES,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct ArchitectureSection {
pub max_file_lines: usize,
pub huge_file_lines: usize,
pub max_directory_modules: usize,
pub max_directory_depth: usize,
pub max_function_lines: usize,
pub max_fan_out: usize,
pub instability_hub_min_fan_in: usize,
pub instability_hub_min_instability_pct: usize,
pub module_mappings: std::collections::BTreeMap<String, Vec<String>>,
#[serde(default)]
pub layers: Vec<LayerRule>,
#[serde(default)]
pub package_roots: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct LayerRule {
pub name: String,
#[serde(default)]
pub paths: Vec<String>,
}
impl Default for ArchitectureSection {
fn default() -> Self {
Self {
max_file_lines: DEFAULT_MAX_FILE_LINES,
huge_file_lines: DEFAULT_HUGE_FILE_LINES,
max_directory_modules: DEFAULT_MAX_DIRECTORY_MODULES,
max_directory_depth: DEFAULT_MAX_DIRECTORY_DEPTH,
max_function_lines: DEFAULT_LONG_FUNCTION_LINES,
max_fan_out: DEFAULT_MAX_FAN_OUT,
instability_hub_min_fan_in: DEFAULT_INSTABILITY_HUB_MIN_FAN_IN,
instability_hub_min_instability_pct: DEFAULT_INSTABILITY_HUB_MIN_INSTABILITY_PCT,
module_mappings: std::collections::BTreeMap::new(),
layers: Vec::new(),
package_roots: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct CodeQualitySection {
pub complexity_medium_threshold: usize,
pub complexity_high_threshold: usize,
pub max_control_flow_depth: usize,
pub complex_function_threshold: usize,
}
impl Default for CodeQualitySection {
fn default() -> Self {
Self {
complexity_medium_threshold: DEFAULT_COMPLEXITY_MEDIUM_THRESHOLD,
complexity_high_threshold: DEFAULT_COMPLEXITY_HIGH_THRESHOLD,
max_control_flow_depth: DEFAULT_MAX_CONTROL_FLOW_DEPTH,
complex_function_threshold: DEFAULT_COMPLEX_FUNCTION_THRESHOLD,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct TestingSection {
pub detect_missing_tests: bool,
}
impl Default for TestingSection {
fn default() -> Self {
Self {
detect_missing_tests: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct SecuritySection {
pub detect_secret_like_names: bool,
}
impl Default for SecuritySection {
fn default() -> Self {
Self {
detect_secret_like_names: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct SecurityBoundarySection {
pub enabled: bool,
pub extra_patterns: Vec<String>,
}
impl Default for SecurityBoundarySection {
fn default() -> Self {
Self {
enabled: true,
extra_patterns: Vec::new(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct BehavioralSection {
pub enabled: bool,
}
impl Default for BehavioralSection {
fn default() -> Self {
Self { enabled: true }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct AlgorithmicSection {
pub enabled: bool,
}
impl Default for AlgorithmicSection {
fn default() -> Self {
Self { enabled: true }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct TaintSection {
pub enabled: bool,
}
impl Default for TaintSection {
fn default() -> Self {
Self { enabled: true }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(default)]
pub struct OutputSection {
pub default_format: OutputFormat,
}
impl Default for OutputSection {
fn default() -> Self {
Self {
default_format: OutputFormat::Console,
}
}
}