1use crate::types::{
7 Constraint, SoftDeletePattern, ErrorHandlingPattern, NamingPattern,
8 ResourceManagementPattern, ValidationPattern, TestIdiomPattern,
9 ImportPattern, TypeCoveragePattern, ApiConventionPattern, AsyncPattern,
10 NamingConvention, ImportStyle, ImportGrouping,
11};
12
13pub struct DetectedPatterns<'a> {
15 pub soft_delete: &'a Option<SoftDeletePattern>,
17 pub error_handling: &'a Option<ErrorHandlingPattern>,
19 pub naming: &'a Option<NamingPattern>,
21 pub resource_management: &'a Option<ResourceManagementPattern>,
23 pub validation: &'a Option<ValidationPattern>,
25 pub test_idioms: &'a Option<TestIdiomPattern>,
27 pub import_patterns: &'a Option<ImportPattern>,
29 pub type_coverage: &'a Option<TypeCoveragePattern>,
31 pub api_conventions: &'a Option<ApiConventionPattern>,
33 pub async_patterns: &'a Option<AsyncPattern>,
35}
36
37pub fn generate_constraints(patterns: &DetectedPatterns<'_>) -> Vec<Constraint> {
39 let mut constraints = Vec::new();
40
41 add_soft_delete_constraints(patterns.soft_delete, &mut constraints);
42 add_error_handling_constraints(patterns.error_handling, &mut constraints);
43 add_naming_constraints(patterns.naming, &mut constraints);
44 add_resource_management_constraints(patterns.resource_management, &mut constraints);
45 add_validation_constraints(patterns.validation, &mut constraints);
46 add_test_constraints(patterns.test_idioms, &mut constraints);
47 add_import_constraints(patterns.import_patterns, &mut constraints);
48 add_type_constraints(patterns.type_coverage, &mut constraints);
49 add_api_constraints(patterns.api_conventions, &mut constraints);
50 add_async_constraints(patterns.async_patterns, &mut constraints);
51
52 constraints.sort_by_key(|c| c.priority);
54
55 constraints
56}
57
58fn add_soft_delete_constraints(
59 soft_delete: &Option<SoftDeletePattern>,
60 constraints: &mut Vec<Constraint>,
61) {
62 let Some(pattern) = soft_delete else {
63 return;
64 };
65 if !pattern.detected || pattern.confidence < 0.4 {
66 return;
67 }
68 constraints.push(Constraint::new(
69 "soft_delete",
70 format!(
71 "Use soft delete pattern with {} fields instead of hard DELETE",
72 pattern.column_names.join(", ")
73 ),
74 pattern.confidence,
75 1,
76 ));
77}
78
79fn add_error_handling_constraints(
80 error_handling: &Option<ErrorHandlingPattern>,
81 constraints: &mut Vec<Constraint>,
82) {
83 let Some(pattern) = error_handling else {
84 return;
85 };
86 if pattern.confidence < 0.3 {
87 return;
88 }
89 if pattern.patterns.contains(&"result_type".to_string()) {
90 constraints.push(Constraint::new(
91 "error_handling",
92 "Use Result<T, E> return types for fallible operations",
93 pattern.confidence,
94 1,
95 ));
96 }
97 if pattern.patterns.contains(&"try_catch".to_string()) {
98 constraints.push(Constraint::new(
99 "error_handling",
100 "Wrap error-prone operations in try/catch blocks with specific exception handling",
101 pattern.confidence,
102 2,
103 ));
104 }
105 if pattern.patterns.contains(&"custom_errors".to_string()) && !pattern.exception_types.is_empty() {
106 constraints.push(Constraint::new(
107 "error_handling",
108 format!(
109 "Use existing custom error types: {}",
110 pattern.exception_types.join(", ")
111 ),
112 pattern.confidence,
113 2,
114 ));
115 }
116}
117
118fn add_naming_constraints(naming: &Option<NamingPattern>, constraints: &mut Vec<Constraint>) {
119 let Some(pattern) = naming else {
120 return;
121 };
122 if pattern.consistency_score < 0.5 {
123 return;
124 }
125 constraints.push(Constraint::new(
126 "naming",
127 format!(
128 "Function names: {}, Class names: {}, Constants: {}",
129 convention_to_string(&pattern.functions),
130 convention_to_string(&pattern.classes),
131 convention_to_string(&pattern.constants),
132 ),
133 pattern.consistency_score,
134 1,
135 ));
136 if let Some(ref prefix) = pattern.private_prefix {
137 constraints.push(Constraint::new(
138 "naming",
139 format!("Use '{}' prefix for private members", prefix),
140 pattern.consistency_score,
141 2,
142 ));
143 }
144}
145
146fn add_resource_management_constraints(
147 resource_management: &Option<ResourceManagementPattern>,
148 constraints: &mut Vec<Constraint>,
149) {
150 let Some(pattern) = resource_management else {
151 return;
152 };
153 if pattern.confidence < 0.4 {
154 return;
155 }
156 for p in &pattern.patterns {
157 let rule = match p.as_str() {
158 "context_manager" => "Use 'with' statements (context managers) for resource management",
159 "defer" => "Use 'defer' to ensure cleanup runs even on error",
160 "raii" => "Implement Drop trait for types that manage external resources",
161 "finally" => "Use try/finally for explicit resource cleanup",
162 _ => continue,
163 };
164 constraints.push(Constraint::new(
165 "resource_management",
166 rule,
167 pattern.confidence,
168 1,
169 ));
170 }
171}
172
173fn add_validation_constraints(
174 validation: &Option<ValidationPattern>,
175 constraints: &mut Vec<Constraint>,
176) {
177 let Some(pattern) = validation else {
178 return;
179 };
180 if pattern.confidence < 0.3 {
181 return;
182 }
183 if !pattern.frameworks.is_empty() {
184 constraints.push(Constraint::new(
185 "validation",
186 format!("Use {} for input validation", pattern.frameworks.join(" or ")),
187 pattern.confidence,
188 1,
189 ));
190 }
191 if pattern.patterns.contains(&"guard_clauses".to_string()) {
192 constraints.push(Constraint::new(
193 "validation",
194 "Validate inputs at function start with guard clauses",
195 pattern.confidence,
196 2,
197 ));
198 }
199}
200
201fn add_test_constraints(test_idioms: &Option<TestIdiomPattern>, constraints: &mut Vec<Constraint>) {
202 let Some(pattern) = test_idioms else {
203 return;
204 };
205 if pattern.confidence < 0.3 {
206 return;
207 }
208 if let Some(ref framework) = pattern.framework {
209 constraints.push(Constraint::new(
210 "testing",
211 format!("Use {} testing framework", framework),
212 pattern.confidence,
213 1,
214 ));
215 }
216 if pattern.fixture_usage {
217 constraints.push(Constraint::new(
218 "testing",
219 "Use fixtures for test setup/teardown",
220 pattern.confidence,
221 2,
222 ));
223 }
224 if pattern.mock_usage {
225 constraints.push(Constraint::new(
226 "testing",
227 "Use mocking for external dependencies in tests",
228 pattern.confidence,
229 2,
230 ));
231 }
232}
233
234fn add_import_constraints(import_patterns: &Option<ImportPattern>, constraints: &mut Vec<Constraint>) {
235 let Some(pattern) = import_patterns else {
236 return;
237 };
238 match pattern.absolute_vs_relative {
239 ImportStyle::Absolute => constraints.push(Constraint::new(
240 "imports",
241 "Prefer absolute imports over relative imports",
242 1.0,
243 2,
244 )),
245 ImportStyle::Relative => constraints.push(Constraint::new(
246 "imports",
247 "Prefer relative imports for local modules",
248 1.0,
249 2,
250 )),
251 ImportStyle::Mixed => {}
252 }
253 match pattern.grouping_style {
254 ImportGrouping::StdlibFirst => constraints.push(Constraint::new(
255 "imports",
256 "Group imports: stdlib first, then third-party, then local",
257 1.0,
258 3,
259 )),
260 ImportGrouping::ThirdPartyFirst => constraints.push(Constraint::new(
261 "imports",
262 "Group imports: third-party first, then stdlib, then local",
263 1.0,
264 3,
265 )),
266 _ => {}
267 }
268}
269
270fn add_type_constraints(type_coverage: &Option<TypeCoveragePattern>, constraints: &mut Vec<Constraint>) {
271 let Some(pattern) = type_coverage else {
272 return;
273 };
274 if pattern.coverage_overall >= 0.5 {
275 constraints.push(Constraint::new(
276 "types",
277 "Add type annotations to function parameters and return values",
278 pattern.coverage_overall,
279 1,
280 ));
281 }
282 if pattern.typevar_usage {
283 constraints.push(Constraint::new(
284 "types",
285 "Use generics/TypeVar for reusable type-safe functions",
286 pattern.coverage_overall,
287 2,
288 ));
289 }
290}
291
292fn add_api_constraints(api_conventions: &Option<ApiConventionPattern>, constraints: &mut Vec<Constraint>) {
293 let Some(pattern) = api_conventions else {
294 return;
295 };
296 if pattern.confidence < 0.4 {
297 return;
298 }
299 if let Some(ref framework) = pattern.framework {
300 constraints.push(Constraint::new(
301 "api",
302 format!("Follow {} patterns for API endpoints", framework),
303 pattern.confidence,
304 1,
305 ));
306 }
307 if pattern.patterns.contains(&"rest_crud".to_string()) {
308 constraints.push(Constraint::new(
309 "api",
310 "Follow REST conventions: GET for read, POST for create, PUT for update, DELETE for delete",
311 pattern.confidence,
312 1,
313 ));
314 }
315 if let Some(ref orm) = pattern.orm_usage {
316 constraints.push(Constraint::new(
317 "api",
318 format!("Use {} for database operations", orm),
319 pattern.confidence,
320 2,
321 ));
322 }
323}
324
325fn add_async_constraints(async_patterns: &Option<AsyncPattern>, constraints: &mut Vec<Constraint>) {
326 let Some(pattern) = async_patterns else {
327 return;
328 };
329 if pattern.concurrency_confidence < 0.3 {
330 return;
331 }
332 if pattern.patterns.contains(&"async_await".to_string()) {
333 constraints.push(Constraint::new(
334 "async",
335 "Use async/await for asynchronous operations",
336 pattern.concurrency_confidence,
337 1,
338 ));
339 }
340 if pattern.patterns.contains(&"goroutines".to_string()) {
341 constraints.push(Constraint::new(
342 "async",
343 "Use goroutines for concurrent operations",
344 pattern.concurrency_confidence,
345 1,
346 ));
347 }
348 if !pattern.sync_primitives.is_empty() {
349 constraints.push(Constraint::new(
350 "async",
351 format!(
352 "Use {} for thread synchronization",
353 pattern.sync_primitives.join(", ")
354 ),
355 pattern.concurrency_confidence,
356 2,
357 ));
358 }
359}
360
361fn convention_to_string(conv: &NamingConvention) -> &'static str {
362 match conv {
363 NamingConvention::SnakeCase => "snake_case",
364 NamingConvention::CamelCase => "camelCase",
365 NamingConvention::PascalCase => "PascalCase",
366 NamingConvention::UpperSnakeCase => "UPPER_SNAKE_CASE",
367 NamingConvention::Mixed => "mixed",
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374
375 #[test]
376 fn test_soft_delete_constraint() {
377 let soft_delete = Some(SoftDeletePattern {
378 detected: true,
379 confidence: 0.8,
380 column_names: vec!["is_deleted".to_string(), "deleted_at".to_string()],
381 evidence: vec![],
382 });
383
384 let constraints = generate_constraints(&DetectedPatterns {
385 soft_delete: &soft_delete,
386 error_handling: &None,
387 naming: &None,
388 resource_management: &None,
389 validation: &None,
390 test_idioms: &None,
391 import_patterns: &None,
392 type_coverage: &None,
393 api_conventions: &None,
394 async_patterns: &None,
395 });
396
397 assert!(!constraints.is_empty());
398 assert!(constraints[0].rule.contains("soft delete"));
399 assert!(constraints[0].rule.contains("is_deleted"));
400 }
401
402 #[test]
403 fn test_naming_constraint() {
404 let naming = Some(NamingPattern {
405 functions: NamingConvention::SnakeCase,
406 classes: NamingConvention::PascalCase,
407 constants: NamingConvention::UpperSnakeCase,
408 private_prefix: Some("_".to_string()),
409 consistency_score: 0.9,
410 violations: vec![],
411 });
412
413 let constraints = generate_constraints(&DetectedPatterns {
414 soft_delete: &None,
415 error_handling: &None,
416 naming: &naming,
417 resource_management: &None,
418 validation: &None,
419 test_idioms: &None,
420 import_patterns: &None,
421 type_coverage: &None,
422 api_conventions: &None,
423 async_patterns: &None,
424 });
425
426 assert!(constraints.len() >= 2);
427 assert!(constraints.iter().any(|c| c.rule.contains("snake_case")));
428 assert!(constraints.iter().any(|c| c.rule.contains("_")));
429 }
430
431 #[test]
432 fn test_no_constraints_below_threshold() {
433 let soft_delete = Some(SoftDeletePattern {
434 detected: true,
435 confidence: 0.2, column_names: vec!["is_deleted".to_string()],
437 evidence: vec![],
438 });
439
440 let constraints = generate_constraints(&DetectedPatterns {
441 soft_delete: &soft_delete,
442 error_handling: &None,
443 naming: &None,
444 resource_management: &None,
445 validation: &None,
446 test_idioms: &None,
447 import_patterns: &None,
448 type_coverage: &None,
449 api_conventions: &None,
450 async_patterns: &None,
451 });
452
453 assert!(constraints.is_empty());
454 }
455}