Skip to main content

smelt_validator/
config.rs

1//! Validation configuration
2
3use crate::error::ValidationResult;
4use serde::{Deserialize, Serialize};
5use std::path::Path;
6
7/// Smelt validation configuration
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9pub struct ValidationConfig {
10    /// Architectural validation rules
11    #[serde(default)]
12    pub architecture: ArchitectureConfig,
13
14    /// Semantic delta validation rules
15    #[serde(default)]
16    pub semantic: SemanticConfig,
17
18    /// Intent validation rules
19    #[serde(default)]
20    pub intent: IntentConfig,
21}
22
23/// Architectural validation configuration (Crucible-compatible)
24#[derive(Debug, Clone, Default, Serialize, Deserialize)]
25pub struct ArchitectureConfig {
26    /// Layer definitions
27    #[serde(default)]
28    pub layers: Vec<Layer>,
29
30    /// Whether to check for circular dependencies
31    #[serde(default = "default_true")]
32    pub check_circular_deps: bool,
33
34    /// Whether to enforce layer boundaries
35    #[serde(default = "default_true")]
36    pub enforce_layers: bool,
37}
38
39/// Layer definition for architectural boundaries
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct Layer {
42    /// Layer name
43    pub name: String,
44
45    /// Path patterns that belong to this layer
46    pub paths: Vec<String>,
47
48    /// Layers this layer can depend on
49    #[serde(default)]
50    pub can_depend_on: Vec<String>,
51
52    /// Layers this layer cannot depend on
53    #[serde(default)]
54    pub prohibited_dependencies: Vec<String>,
55}
56
57/// Semantic delta validation configuration
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct SemanticConfig {
60    /// Whether to check for breaking changes
61    #[serde(default = "default_true")]
62    pub check_breaking_changes: bool,
63
64    /// Whether breaking changes are errors or warnings
65    #[serde(default = "default_true")]
66    pub breaking_changes_error: bool,
67
68    /// Whether to check visibility changes
69    #[serde(default = "default_true")]
70    pub check_visibility: bool,
71
72    /// Whether new public API needs review
73    #[serde(default)]
74    pub review_new_public_api: bool,
75
76    /// Complexity thresholds
77    #[serde(default)]
78    pub complexity: ComplexityConfig,
79}
80
81impl Default for SemanticConfig {
82    fn default() -> Self {
83        Self {
84            check_breaking_changes: true,
85            breaking_changes_error: true,
86            check_visibility: true,
87            review_new_public_api: false,
88            complexity: ComplexityConfig::default(),
89        }
90    }
91}
92
93/// Complexity validation thresholds
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct ComplexityConfig {
96    /// Maximum allowed cyclomatic complexity per function
97    #[serde(default = "default_complexity")]
98    pub max_cyclomatic: u32,
99
100    /// Maximum allowed cognitive complexity per function
101    #[serde(default = "default_cognitive")]
102    pub max_cognitive: u32,
103
104    /// Maximum allowed complexity increase in a single delta
105    #[serde(default = "default_delta")]
106    pub max_complexity_increase: i32,
107
108    /// Whether complexity violations are errors
109    #[serde(default)]
110    pub complexity_error: bool,
111}
112
113impl Default for ComplexityConfig {
114    fn default() -> Self {
115        Self {
116            max_cyclomatic: 15,
117            max_cognitive: 25,
118            max_complexity_increase: 10,
119            complexity_error: false,
120        }
121    }
122}
123
124/// Intent validation configuration
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct IntentConfig {
127    /// Whether to validate delta matches intent
128    #[serde(default = "default_true")]
129    pub validate_scope: bool,
130
131    /// Whether to require rationale for large changes
132    #[serde(default)]
133    pub require_rationale_for_large_changes: bool,
134
135    /// Threshold for "large" changes (file count)
136    #[serde(default = "default_large_threshold")]
137    pub large_change_threshold: usize,
138}
139
140impl Default for IntentConfig {
141    fn default() -> Self {
142        Self {
143            validate_scope: true,
144            require_rationale_for_large_changes: false,
145            large_change_threshold: 10,
146        }
147    }
148}
149
150fn default_true() -> bool {
151    true
152}
153
154fn default_complexity() -> u32 {
155    15
156}
157
158fn default_cognitive() -> u32 {
159    25
160}
161
162fn default_delta() -> i32 {
163    10
164}
165
166fn default_large_threshold() -> usize {
167    10
168}
169
170impl ValidationConfig {
171    /// Load configuration from a YAML file
172    pub fn load(path: &Path) -> ValidationResult<Self> {
173        let content = std::fs::read_to_string(path)?;
174        let config: Self = serde_yaml::from_str(&content)?;
175        Ok(config)
176    }
177
178    /// Load configuration from smelt directory, with fallback to defaults
179    pub fn load_or_default(smelt_dir: &Path) -> Self {
180        // Try crucible.yaml first
181        let crucible_path = smelt_dir.join("crucible.yaml");
182        if crucible_path.exists() {
183            if let Ok(config) = Self::load(&crucible_path) {
184                return config;
185            }
186        }
187
188        // Try smelt validation config
189        let validation_path = smelt_dir.join("validation.yaml");
190        if validation_path.exists() {
191            if let Ok(config) = Self::load(&validation_path) {
192                return config;
193            }
194        }
195
196        // Return defaults
197        Self::default()
198    }
199
200    /// Create a strict configuration for production use
201    pub fn strict() -> Self {
202        Self {
203            architecture: ArchitectureConfig {
204                check_circular_deps: true,
205                enforce_layers: true,
206                layers: Vec::new(),
207            },
208            semantic: SemanticConfig {
209                check_breaking_changes: true,
210                breaking_changes_error: true,
211                check_visibility: true,
212                review_new_public_api: true,
213                complexity: ComplexityConfig {
214                    max_cyclomatic: 10,
215                    max_cognitive: 15,
216                    max_complexity_increase: 5,
217                    complexity_error: true,
218                },
219            },
220            intent: IntentConfig {
221                validate_scope: true,
222                require_rationale_for_large_changes: true,
223                large_change_threshold: 5,
224            },
225        }
226    }
227}