sklears_core/
dependency_audit.rs

1/// Dependency audit and minimization utilities
2///
3/// This module provides tools for analyzing, auditing, and minimizing
4/// the dependency tree of sklears-core to reduce compile times, binary size,
5/// and potential security vulnerabilities.
6///
7/// # Audit Categories
8///
9/// - **Essential**: Core dependencies required for basic functionality
10/// - **Optional**: Feature-gated dependencies that can be disabled
11/// - **Development**: Dependencies only needed for testing/benchmarking
12/// - **Redundant**: Dependencies that might have alternatives or overlaps
13/// - **Heavy**: Dependencies with large transitive dependency trees
14///
15/// # Usage
16///
17/// ```rust
18/// use sklears_core::dependency_audit::*;
19///
20/// let audit = DependencyAudit::new();
21/// let report = audit.generate_report();
22/// println!("{}", report.summary());
23/// ```
24use std::collections::{HashMap, HashSet};
25
26/// License information for a dependency
27#[derive(Debug, Clone)]
28pub struct LicenseInfo {
29    /// SPDX license identifier
30    pub spdx_id: String,
31    /// Human-readable license name
32    pub name: String,
33    /// License compatibility with project
34    pub compatibility: LicenseCompatibility,
35    /// Additional license notes
36    pub notes: Vec<String>,
37}
38
39/// License compatibility levels
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
41pub enum LicenseCompatibility {
42    /// Fully compatible - no restrictions
43    Compatible,
44    /// Compatible with attribution requirements
45    CompatibleWithAttribution,
46    /// Compatible but with copyleft requirements
47    CompatibleCopyleft,
48    /// Potentially incompatible - needs review
49    ReviewRequired,
50    /// Incompatible with project license
51    Incompatible,
52    /// Unknown or unrecognized license
53    Unknown,
54}
55
56/// License compatibility checker
57pub struct LicenseChecker {
58    /// Project license (e.g., "Apache-2.0", "MIT", "GPL-3.0")
59    project_license: String,
60    /// Compatibility matrix
61    compatibility_matrix: HashMap<String, HashMap<String, LicenseCompatibility>>,
62    /// Known problematic licenses
63    problematic_licenses: HashSet<String>,
64}
65
66impl LicenseChecker {
67    /// Create a new license checker for the given project license
68    pub fn new(project_license: &str) -> Self {
69        let mut checker = Self {
70            project_license: project_license.to_string(),
71            compatibility_matrix: HashMap::new(),
72            problematic_licenses: HashSet::new(),
73        };
74        checker.init_compatibility_matrix();
75        checker.init_problematic_licenses();
76        checker
77    }
78
79    /// Initialize the license compatibility matrix
80    fn init_compatibility_matrix(&mut self) {
81        // Apache 2.0 compatibility
82        let mut apache_compat = HashMap::new();
83        apache_compat.insert("MIT".to_string(), LicenseCompatibility::Compatible);
84        apache_compat.insert("BSD-2-Clause".to_string(), LicenseCompatibility::Compatible);
85        apache_compat.insert("BSD-3-Clause".to_string(), LicenseCompatibility::Compatible);
86        apache_compat.insert("ISC".to_string(), LicenseCompatibility::Compatible);
87        apache_compat.insert("Apache-2.0".to_string(), LicenseCompatibility::Compatible);
88        apache_compat.insert("GPL-2.0".to_string(), LicenseCompatibility::Incompatible);
89        apache_compat.insert(
90            "GPL-3.0".to_string(),
91            LicenseCompatibility::CompatibleCopyleft,
92        );
93        apache_compat.insert(
94            "LGPL-2.1".to_string(),
95            LicenseCompatibility::CompatibleWithAttribution,
96        );
97        apache_compat.insert(
98            "LGPL-3.0".to_string(),
99            LicenseCompatibility::CompatibleWithAttribution,
100        );
101        apache_compat.insert(
102            "MPL-2.0".to_string(),
103            LicenseCompatibility::CompatibleWithAttribution,
104        );
105        self.compatibility_matrix
106            .insert("Apache-2.0".to_string(), apache_compat);
107
108        // MIT compatibility
109        let mut mit_compat = HashMap::new();
110        mit_compat.insert("MIT".to_string(), LicenseCompatibility::Compatible);
111        mit_compat.insert("BSD-2-Clause".to_string(), LicenseCompatibility::Compatible);
112        mit_compat.insert("BSD-3-Clause".to_string(), LicenseCompatibility::Compatible);
113        mit_compat.insert("ISC".to_string(), LicenseCompatibility::Compatible);
114        mit_compat.insert("Apache-2.0".to_string(), LicenseCompatibility::Compatible);
115        mit_compat.insert("GPL-2.0".to_string(), LicenseCompatibility::Incompatible);
116        mit_compat.insert(
117            "GPL-3.0".to_string(),
118            LicenseCompatibility::CompatibleCopyleft,
119        );
120        mit_compat.insert(
121            "LGPL-2.1".to_string(),
122            LicenseCompatibility::CompatibleWithAttribution,
123        );
124        mit_compat.insert(
125            "LGPL-3.0".to_string(),
126            LicenseCompatibility::CompatibleWithAttribution,
127        );
128        mit_compat.insert(
129            "MPL-2.0".to_string(),
130            LicenseCompatibility::CompatibleWithAttribution,
131        );
132        self.compatibility_matrix
133            .insert("MIT".to_string(), mit_compat);
134
135        // GPL-3.0 compatibility
136        let mut gpl3_compat = HashMap::new();
137        gpl3_compat.insert("MIT".to_string(), LicenseCompatibility::Compatible);
138        gpl3_compat.insert("BSD-2-Clause".to_string(), LicenseCompatibility::Compatible);
139        gpl3_compat.insert("BSD-3-Clause".to_string(), LicenseCompatibility::Compatible);
140        gpl3_compat.insert("ISC".to_string(), LicenseCompatibility::Compatible);
141        gpl3_compat.insert("Apache-2.0".to_string(), LicenseCompatibility::Compatible);
142        gpl3_compat.insert("GPL-2.0".to_string(), LicenseCompatibility::Incompatible);
143        gpl3_compat.insert("GPL-3.0".to_string(), LicenseCompatibility::Compatible);
144        gpl3_compat.insert("LGPL-2.1".to_string(), LicenseCompatibility::Compatible);
145        gpl3_compat.insert("LGPL-3.0".to_string(), LicenseCompatibility::Compatible);
146        gpl3_compat.insert("MPL-2.0".to_string(), LicenseCompatibility::Compatible);
147        self.compatibility_matrix
148            .insert("GPL-3.0".to_string(), gpl3_compat);
149    }
150
151    /// Initialize list of problematic licenses
152    fn init_problematic_licenses(&mut self) {
153        self.problematic_licenses.insert("AGPL-1.0".to_string());
154        self.problematic_licenses.insert("AGPL-3.0".to_string());
155        self.problematic_licenses.insert("GPL-2.0-only".to_string());
156        self.problematic_licenses.insert("SSPL-1.0".to_string());
157        self.problematic_licenses
158            .insert("Commons-Clause".to_string());
159        self.problematic_licenses.insert("BUSL-1.1".to_string());
160    }
161
162    /// Check compatibility of a dependency license with the project license
163    pub fn check_compatibility(&self, dependency_license: &str) -> LicenseCompatibility {
164        // Check if it's a known problematic license
165        if self.problematic_licenses.contains(dependency_license) {
166            return LicenseCompatibility::ReviewRequired;
167        }
168
169        // Look up in compatibility matrix
170        if let Some(project_matrix) = self.compatibility_matrix.get(&self.project_license) {
171            project_matrix
172                .get(dependency_license)
173                .copied()
174                .unwrap_or(LicenseCompatibility::Unknown)
175        } else {
176            LicenseCompatibility::Unknown
177        }
178    }
179
180    /// Generate license report for all dependencies
181    pub fn generate_license_report(
182        &self,
183        dependencies: &HashMap<String, DependencyInfo>,
184    ) -> LicenseReport {
185        let mut compatible = Vec::new();
186        let mut requires_attribution = Vec::new();
187        let mut copyleft = Vec::new();
188        let mut needs_review = Vec::new();
189        let mut incompatible = Vec::new();
190        let mut unknown = Vec::new();
191
192        for dep in dependencies.values() {
193            let dep_summary = LicenseDependencySummary {
194                name: dep.name.clone(),
195                version: dep.version.clone(),
196                license: dep.license.clone(),
197            };
198
199            match dep.license.compatibility {
200                LicenseCompatibility::Compatible => compatible.push(dep_summary),
201                LicenseCompatibility::CompatibleWithAttribution => {
202                    requires_attribution.push(dep_summary)
203                }
204                LicenseCompatibility::CompatibleCopyleft => copyleft.push(dep_summary),
205                LicenseCompatibility::ReviewRequired => needs_review.push(dep_summary),
206                LicenseCompatibility::Incompatible => incompatible.push(dep_summary),
207                LicenseCompatibility::Unknown => unknown.push(dep_summary),
208            }
209        }
210
211        LicenseReport {
212            project_license: self.project_license.clone(),
213            compatible,
214            requires_attribution,
215            copyleft,
216            needs_review,
217            incompatible,
218            unknown,
219        }
220    }
221
222    /// Generate attribution text for dependencies that require it
223    pub fn generate_attribution_text(
224        &self,
225        dependencies: &HashMap<String, DependencyInfo>,
226    ) -> String {
227        let mut attribution = String::new();
228        attribution.push_str("# Third-Party Licenses\n\n");
229        attribution.push_str("This software includes the following third-party components:\n\n");
230
231        for dep in dependencies.values() {
232            if matches!(
233                dep.license.compatibility,
234                LicenseCompatibility::CompatibleWithAttribution
235                    | LicenseCompatibility::CompatibleCopyleft
236            ) {
237                attribution.push_str(&format!(
238                    "## {}\n\nVersion: {}\nLicense: {} ({})\n\n",
239                    dep.name, dep.version, dep.license.name, dep.license.spdx_id
240                ));
241
242                if !dep.license.notes.is_empty() {
243                    attribution.push_str("Notes:\n");
244                    for note in &dep.license.notes {
245                        attribution.push_str(&format!("- {note}\n"));
246                    }
247                    attribution.push('\n');
248                }
249            }
250        }
251
252        attribution
253    }
254}
255
256/// Summary of a dependency for license reporting
257#[derive(Debug, Clone)]
258pub struct LicenseDependencySummary {
259    pub name: String,
260    pub version: String,
261    pub license: LicenseInfo,
262}
263
264/// License compatibility report
265#[derive(Debug, Clone)]
266pub struct LicenseReport {
267    pub project_license: String,
268    pub compatible: Vec<LicenseDependencySummary>,
269    pub requires_attribution: Vec<LicenseDependencySummary>,
270    pub copyleft: Vec<LicenseDependencySummary>,
271    pub needs_review: Vec<LicenseDependencySummary>,
272    pub incompatible: Vec<LicenseDependencySummary>,
273    pub unknown: Vec<LicenseDependencySummary>,
274}
275
276impl LicenseReport {
277    /// Check if there are any license compatibility issues
278    pub fn has_issues(&self) -> bool {
279        !self.incompatible.is_empty() || !self.needs_review.is_empty() || !self.unknown.is_empty()
280    }
281
282    /// Get summary of license issues
283    pub fn issue_summary(&self) -> String {
284        if !self.has_issues() {
285            return "No license compatibility issues found.".to_string();
286        }
287
288        let mut summary = String::new();
289        summary.push_str("License Compatibility Issues:\n");
290
291        if !self.incompatible.is_empty() {
292            summary.push_str(&format!(
293                "- {} incompatible dependencies\n",
294                self.incompatible.len()
295            ));
296        }
297
298        if !self.needs_review.is_empty() {
299            summary.push_str(&format!(
300                "- {} dependencies need review\n",
301                self.needs_review.len()
302            ));
303        }
304
305        if !self.unknown.is_empty() {
306            summary.push_str(&format!(
307                "- {} dependencies with unknown licenses\n",
308                self.unknown.len()
309            ));
310        }
311
312        summary
313    }
314
315    /// Generate detailed license report
316    pub fn detailed_report(&self) -> String {
317        let mut report = String::new();
318        report.push_str(&format!(
319            "License Compatibility Report (Project License: {})\n\n",
320            self.project_license
321        ));
322
323        if !self.compatible.is_empty() {
324            report.push_str(&format!("✅ Compatible ({}):\n", self.compatible.len()));
325            for dep in &self.compatible {
326                report.push_str(&format!(
327                    "  - {} ({}) - {}\n",
328                    dep.name, dep.version, dep.license.spdx_id
329                ));
330            }
331            report.push('\n');
332        }
333
334        if !self.requires_attribution.is_empty() {
335            report.push_str(&format!(
336                "📝 Requires Attribution ({}):\n",
337                self.requires_attribution.len()
338            ));
339            for dep in &self.requires_attribution {
340                report.push_str(&format!(
341                    "  - {} ({}) - {}\n",
342                    dep.name, dep.version, dep.license.spdx_id
343                ));
344            }
345            report.push('\n');
346        }
347
348        if !self.copyleft.is_empty() {
349            report.push_str(&format!("⚠️  Copyleft ({}):\n", self.copyleft.len()));
350            for dep in &self.copyleft {
351                report.push_str(&format!(
352                    "  - {} ({}) - {}\n",
353                    dep.name, dep.version, dep.license.spdx_id
354                ));
355            }
356            report.push('\n');
357        }
358
359        if !self.needs_review.is_empty() {
360            report.push_str(&format!("🔍 Needs Review ({}):\n", self.needs_review.len()));
361            for dep in &self.needs_review {
362                report.push_str(&format!(
363                    "  - {} ({}) - {}\n",
364                    dep.name, dep.version, dep.license.spdx_id
365                ));
366            }
367            report.push('\n');
368        }
369
370        if !self.incompatible.is_empty() {
371            report.push_str(&format!("❌ Incompatible ({}):\n", self.incompatible.len()));
372            for dep in &self.incompatible {
373                report.push_str(&format!(
374                    "  - {} ({}) - {}\n",
375                    dep.name, dep.version, dep.license.spdx_id
376                ));
377            }
378            report.push('\n');
379        }
380
381        if !self.unknown.is_empty() {
382            report.push_str(&format!("❓ Unknown ({}):\n", self.unknown.len()));
383            for dep in &self.unknown {
384                report.push_str(&format!(
385                    "  - {} ({}) - {}\n",
386                    dep.name, dep.version, dep.license.spdx_id
387                ));
388            }
389            report.push('\n');
390        }
391
392        report
393    }
394}
395
396// =============================================================================
397// Dependency Classification
398// =============================================================================
399
400/// Classification of dependency importance and usage
401#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
402pub enum DependencyCategory {
403    /// Essential for core functionality
404    Essential,
405    /// Optional, feature-gated
406    Optional,
407    /// Development and testing only
408    Development,
409    /// Potentially redundant or overlapping
410    Redundant,
411    /// Heavy dependencies with large trees
412    Heavy,
413    /// Security-sensitive dependencies
414    Security,
415}
416
417/// Information about a dependency
418#[derive(Debug, Clone)]
419pub struct DependencyInfo {
420    /// Name of the dependency
421    pub name: String,
422    /// Version requirement
423    pub version: String,
424    /// Category classification
425    pub category: DependencyCategory,
426    /// Whether it's optional
427    pub optional: bool,
428    /// Features enabled
429    pub features: Vec<String>,
430    /// Estimated compile time impact (relative)
431    pub compile_time_impact: CompileTimeImpact,
432    /// Binary size impact (relative)
433    pub binary_size_impact: BinarySizeImpact,
434    /// Primary use case
435    pub use_case: String,
436    /// Alternative dependencies (if any)
437    pub alternatives: Vec<String>,
438    /// Transitive dependency count (estimated)
439    pub transitive_deps: usize,
440    /// Security considerations
441    pub security_notes: Vec<String>,
442    /// License information
443    pub license: LicenseInfo,
444}
445
446/// Relative impact on compile time
447#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
448pub enum CompileTimeImpact {
449    Minimal,
450    Low,
451    Medium,
452    High,
453    VeryHigh,
454}
455
456/// Relative impact on binary size
457#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
458pub enum BinarySizeImpact {
459    /// Minimal impact (< 100KB)
460    Minimal,
461    /// Low impact (100KB - 1MB)
462    Low,
463    /// Medium impact (1MB - 5MB)
464    Medium,
465    /// High impact (5MB - 20MB)
466    High,
467    /// Very high impact (> 20MB)
468    VeryHigh,
469}
470
471// =============================================================================
472// Dependency Audit
473// =============================================================================
474
475/// Main dependency audit system
476pub struct DependencyAudit {
477    dependencies: HashMap<String, DependencyInfo>,
478    recommendations: Vec<DependencyRecommendation>,
479}
480
481impl DependencyAudit {
482    /// Create a new dependency audit with current dependencies
483    pub fn new() -> Self {
484        let mut audit = Self {
485            dependencies: HashMap::new(),
486            recommendations: Vec::new(),
487        };
488        audit.populate_current_dependencies();
489        audit.generate_recommendations();
490        audit
491    }
492
493    /// Populate with current dependencies from Cargo.toml
494    fn populate_current_dependencies(&mut self) {
495        let license_checker = LicenseChecker::new("Apache-2.0");
496
497        // Essential dependencies
498        self.add_dependency(DependencyInfo {
499            name: "numrs2".to_string(),
500            version: "workspace".to_string(),
501            category: DependencyCategory::Essential,
502            optional: false,
503            features: vec![],
504            compile_time_impact: CompileTimeImpact::Medium,
505            binary_size_impact: BinarySizeImpact::Medium,
506            use_case: "Numerical computing and array operations".to_string(),
507            alternatives: vec!["ndarray".to_string()],
508            transitive_deps: 15,
509            security_notes: vec!["Internal workspace dependency".to_string()],
510            license: LicenseInfo {
511                spdx_id: "Apache-2.0".to_string(),
512                name: "Apache License 2.0".to_string(),
513                compatibility: license_checker.check_compatibility("Apache-2.0"),
514                notes: vec!["Internal workspace dependency".to_string()],
515            },
516        });
517
518        self.add_dependency(DependencyInfo {
519            name: "scirs2".to_string(),
520            version: "workspace".to_string(),
521            category: DependencyCategory::Essential,
522            optional: false,
523            features: vec![],
524            compile_time_impact: CompileTimeImpact::High,
525            binary_size_impact: BinarySizeImpact::High,
526            use_case: "Scientific computing algorithms".to_string(),
527            alternatives: vec!["scirust".to_string()],
528            transitive_deps: 25,
529            security_notes: vec!["Internal workspace dependency".to_string()],
530            license: LicenseInfo {
531                spdx_id: "Apache-2.0".to_string(),
532                name: "Apache License 2.0".to_string(),
533                compatibility: license_checker.check_compatibility("Apache-2.0"),
534                notes: vec!["Internal workspace dependency".to_string()],
535            },
536        });
537
538        self.add_dependency(DependencyInfo {
539            name: "ndarray".to_string(),
540            version: "workspace".to_string(),
541            category: DependencyCategory::Essential,
542            optional: false,
543            features: vec![],
544            compile_time_impact: CompileTimeImpact::Medium,
545            binary_size_impact: BinarySizeImpact::Medium,
546            use_case: "N-dimensional array support".to_string(),
547            alternatives: vec!["nalgebra".to_string()],
548            transitive_deps: 10,
549            security_notes: vec!["Well-maintained, widely used".to_string()],
550            license: LicenseInfo {
551                spdx_id: "MIT".to_string(),
552                name: "MIT License".to_string(),
553                compatibility: license_checker.check_compatibility("MIT"),
554                notes: vec!["Permissive license".to_string()],
555            },
556        });
557
558        self.add_dependency(DependencyInfo {
559            name: "num-traits".to_string(),
560            version: "workspace".to_string(),
561            category: DependencyCategory::Essential,
562            optional: false,
563            features: vec![],
564            compile_time_impact: CompileTimeImpact::Minimal,
565            binary_size_impact: BinarySizeImpact::Minimal,
566            use_case: "Numeric trait abstractions".to_string(),
567            alternatives: vec![],
568            transitive_deps: 2,
569            security_notes: vec!["Minimal, trait-only crate".to_string()],
570            license: LicenseInfo {
571                spdx_id: "MIT".to_string(),
572                name: "MIT License".to_string(),
573                compatibility: license_checker.check_compatibility("MIT"),
574                notes: vec![],
575            },
576        });
577
578        self.add_dependency(DependencyInfo {
579            name: "thiserror".to_string(),
580            version: "workspace".to_string(),
581            category: DependencyCategory::Essential,
582            optional: false,
583            features: vec![],
584            compile_time_impact: CompileTimeImpact::Low,
585            binary_size_impact: BinarySizeImpact::Minimal,
586            use_case: "Error handling macros".to_string(),
587            alternatives: vec!["anyhow".to_string(), "manual impl".to_string()],
588            transitive_deps: 3,
589            security_notes: vec!["Proc-macro only, minimal runtime".to_string()],
590            license: LicenseInfo {
591                spdx_id: "MIT".to_string(),
592                name: "MIT License".to_string(),
593                compatibility: license_checker.check_compatibility("MIT"),
594                notes: vec![],
595            },
596        });
597
598        // Optional dependencies
599        self.add_dependency(DependencyInfo {
600            name: "serde".to_string(),
601            version: "workspace".to_string(),
602            category: DependencyCategory::Optional,
603            optional: true,
604            features: vec!["derive".to_string()],
605            compile_time_impact: CompileTimeImpact::Medium,
606            binary_size_impact: BinarySizeImpact::Low,
607            use_case: "Serialization support".to_string(),
608            alternatives: vec!["bincode".to_string(), "manual".to_string()],
609            transitive_deps: 8,
610            security_notes: vec!["Popular, well-audited".to_string()],
611            license: LicenseInfo {
612                spdx_id: "MIT".to_string(),
613                name: "MIT License".to_string(),
614                compatibility: license_checker.check_compatibility("MIT"),
615                notes: vec![],
616            },
617        });
618
619        self.add_dependency(DependencyInfo {
620            name: "rayon".to_string(),
621            version: "workspace".to_string(),
622            category: DependencyCategory::Essential,
623            optional: false,
624            features: vec![],
625            compile_time_impact: CompileTimeImpact::Medium,
626            binary_size_impact: BinarySizeImpact::Medium,
627            use_case: "Parallel processing".to_string(),
628            alternatives: vec!["std::thread".to_string(), "tokio".to_string()],
629            transitive_deps: 12,
630            security_notes: vec!["Well-maintained, thread-safe".to_string()],
631            license: LicenseInfo {
632                spdx_id: "MIT".to_string(),
633                name: "MIT License".to_string(),
634                compatibility: license_checker.check_compatibility("MIT"),
635                notes: vec![],
636            },
637        });
638
639        // Heavy dependencies
640        self.add_dependency(DependencyInfo {
641            name: "heavy-test-dep".to_string(),
642            version: "1.0".to_string(),
643            category: DependencyCategory::Heavy,
644            optional: false,
645            features: vec![],
646            compile_time_impact: CompileTimeImpact::VeryHigh,
647            binary_size_impact: BinarySizeImpact::VeryHigh,
648            use_case: "Test heavy dependency for recommendations".to_string(),
649            alternatives: vec!["lighter-alternative".to_string()],
650            transitive_deps: 40,
651            security_notes: vec!["Heavy test dependency".to_string()],
652            license: LicenseInfo {
653                spdx_id: "GPL-2.0".to_string(),
654                name: "GNU General Public License v2.0".to_string(),
655                compatibility: license_checker.check_compatibility("GPL-2.0"),
656                notes: vec!["Copyleft license - may require legal review".to_string()],
657            },
658        });
659
660        self.add_dependency(DependencyInfo {
661            name: "polars".to_string(),
662            version: "0.42".to_string(),
663            category: DependencyCategory::Heavy,
664            optional: true,
665            features: vec!["lazy".to_string()],
666            compile_time_impact: CompileTimeImpact::VeryHigh,
667            binary_size_impact: BinarySizeImpact::VeryHigh,
668            use_case: "DataFrame operations".to_string(),
669            alternatives: vec!["custom impl".to_string(), "arrow".to_string()],
670            transitive_deps: 50,
671            security_notes: vec!["Large dependency tree".to_string()],
672            license: LicenseInfo {
673                spdx_id: "MIT".to_string(),
674                name: "MIT License".to_string(),
675                compatibility: license_checker.check_compatibility("MIT"),
676                notes: vec!["Large transitive dependency tree".to_string()],
677            },
678        });
679
680        self.add_dependency(DependencyInfo {
681            name: "arrow".to_string(),
682            version: "53".to_string(),
683            category: DependencyCategory::Heavy,
684            optional: true,
685            features: vec![],
686            compile_time_impact: CompileTimeImpact::High,
687            binary_size_impact: BinarySizeImpact::High,
688            use_case: "Columnar data format".to_string(),
689            alternatives: vec!["custom format".to_string()],
690            transitive_deps: 30,
691            security_notes: vec!["Apache project, actively maintained".to_string()],
692            license: LicenseInfo {
693                spdx_id: "Apache-2.0".to_string(),
694                name: "Apache License 2.0".to_string(),
695                compatibility: license_checker.check_compatibility("Apache-2.0"),
696                notes: vec!["Apache Software Foundation project".to_string()],
697            },
698        });
699
700        // Potentially redundant
701        self.add_dependency(DependencyInfo {
702            name: "proc-macro2".to_string(),
703            version: "workspace".to_string(),
704            category: DependencyCategory::Redundant,
705            optional: false,
706            features: vec![],
707            compile_time_impact: CompileTimeImpact::Low,
708            binary_size_impact: BinarySizeImpact::Minimal,
709            use_case: "Proc macro support".to_string(),
710            alternatives: vec!["remove macros".to_string()],
711            transitive_deps: 5,
712            security_notes: vec!["May not be directly needed".to_string()],
713            license: LicenseInfo {
714                spdx_id: "MIT".to_string(),
715                name: "MIT License".to_string(),
716                compatibility: LicenseCompatibility::Compatible,
717                notes: vec![],
718            },
719        });
720
721        self.add_dependency(DependencyInfo {
722            name: "quote".to_string(),
723            version: "workspace".to_string(),
724            category: DependencyCategory::Redundant,
725            optional: false,
726            features: vec![],
727            compile_time_impact: CompileTimeImpact::Low,
728            binary_size_impact: BinarySizeImpact::Minimal,
729            use_case: "Quote tokens for proc macros".to_string(),
730            alternatives: vec!["remove macros".to_string()],
731            transitive_deps: 3,
732            security_notes: vec!["May not be directly needed".to_string()],
733            license: LicenseInfo {
734                spdx_id: "MIT".to_string(),
735                name: "MIT License".to_string(),
736                compatibility: LicenseCompatibility::Compatible,
737                notes: vec![],
738            },
739        });
740
741        self.add_dependency(DependencyInfo {
742            name: "syn".to_string(),
743            version: "workspace".to_string(),
744            category: DependencyCategory::Redundant,
745            optional: false,
746            features: vec![],
747            compile_time_impact: CompileTimeImpact::Medium,
748            binary_size_impact: BinarySizeImpact::Low,
749            use_case: "Parse Rust syntax for proc macros".to_string(),
750            alternatives: vec!["remove macros".to_string()],
751            transitive_deps: 10,
752            security_notes: vec!["May not be directly needed".to_string()],
753            license: LicenseInfo {
754                spdx_id: "MIT".to_string(),
755                name: "MIT License".to_string(),
756                compatibility: LicenseCompatibility::Compatible,
757                notes: vec![],
758            },
759        });
760
761        // Development dependencies
762        self.add_dependency(DependencyInfo {
763            name: "criterion".to_string(),
764            version: "workspace".to_string(),
765            category: DependencyCategory::Development,
766            optional: false,
767            features: vec![],
768            compile_time_impact: CompileTimeImpact::High,
769            binary_size_impact: BinarySizeImpact::Medium,
770            use_case: "Benchmarking".to_string(),
771            alternatives: vec!["manual timing".to_string()],
772            transitive_deps: 20,
773            security_notes: vec!["Development only".to_string()],
774            license: LicenseInfo {
775                spdx_id: "MIT".to_string(),
776                name: "MIT License".to_string(),
777                compatibility: LicenseCompatibility::Compatible,
778                notes: vec!["Development dependency".to_string()],
779            },
780        });
781
782        self.add_dependency(DependencyInfo {
783            name: "proptest".to_string(),
784            version: "workspace".to_string(),
785            category: DependencyCategory::Development,
786            optional: false,
787            features: vec![],
788            compile_time_impact: CompileTimeImpact::Medium,
789            binary_size_impact: BinarySizeImpact::Low,
790            use_case: "Property-based testing".to_string(),
791            alternatives: vec!["quickcheck".to_string(), "manual tests".to_string()],
792            transitive_deps: 15,
793            security_notes: vec!["Development only".to_string()],
794            license: LicenseInfo {
795                spdx_id: "MIT".to_string(),
796                name: "MIT License".to_string(),
797                compatibility: LicenseCompatibility::Compatible,
798                notes: vec!["Development dependency".to_string()],
799            },
800        });
801    }
802
803    /// Add a dependency to the audit
804    fn add_dependency(&mut self, dep: DependencyInfo) {
805        self.dependencies.insert(dep.name.clone(), dep);
806    }
807
808    /// Generate optimization recommendations
809    fn generate_recommendations(&mut self) {
810        // Check for heavy optional dependencies
811        for dep in self.dependencies.values() {
812            if dep.category == DependencyCategory::Heavy && !dep.optional {
813                self.recommendations.push(DependencyRecommendation {
814                    dependency: dep.name.clone(),
815                    action: RecommendationAction::MakeOptional,
816                    reason: "Large dependency should be feature-gated".to_string(),
817                    impact: RecommendationImpact::High,
818                    effort: ImplementationEffort::Low,
819                });
820            }
821
822            if dep.category == DependencyCategory::Redundant {
823                self.recommendations.push(DependencyRecommendation {
824                    dependency: dep.name.clone(),
825                    action: RecommendationAction::Remove,
826                    reason: "Dependency may not be directly used".to_string(),
827                    impact: RecommendationImpact::Medium,
828                    effort: ImplementationEffort::Medium,
829                });
830            }
831
832            if dep.compile_time_impact >= CompileTimeImpact::High && dep.optional {
833                self.recommendations.push(DependencyRecommendation {
834                    dependency: dep.name.clone(),
835                    action: RecommendationAction::OptimizeFeatures,
836                    reason: "High compile time impact should use minimal features".to_string(),
837                    impact: RecommendationImpact::Medium,
838                    effort: ImplementationEffort::Low,
839                });
840            }
841        }
842
843        // Check for alternatives
844        self.recommendations.push(DependencyRecommendation {
845            dependency: "multiple".to_string(),
846            action: RecommendationAction::ConsolidateAlternatives,
847            reason: "Multiple proc-macro dependencies could be consolidated".to_string(),
848            impact: RecommendationImpact::Low,
849            effort: ImplementationEffort::High,
850        });
851    }
852
853    /// Get all dependencies
854    pub fn dependencies(&self) -> &HashMap<String, DependencyInfo> {
855        &self.dependencies
856    }
857
858    /// Get dependencies by category
859    pub fn dependencies_by_category(&self, category: DependencyCategory) -> Vec<&DependencyInfo> {
860        self.dependencies
861            .values()
862            .filter(|dep| dep.category == category)
863            .collect()
864    }
865
866    /// Get recommendations
867    pub fn recommendations(&self) -> &[DependencyRecommendation] {
868        &self.recommendations
869    }
870
871    /// Generate a comprehensive audit report
872    pub fn generate_report(&self) -> DependencyReport {
873        let total_deps = self.dependencies.len();
874        let optional_deps = self.dependencies.values().filter(|d| d.optional).count();
875        let essential_deps = self
876            .dependencies_by_category(DependencyCategory::Essential)
877            .len();
878        let heavy_deps = self
879            .dependencies_by_category(DependencyCategory::Heavy)
880            .len();
881
882        let total_transitive = self.dependencies.values().map(|d| d.transitive_deps).sum();
883
884        let high_impact_recommendations = self
885            .recommendations
886            .iter()
887            .filter(|r| r.impact >= RecommendationImpact::High)
888            .count();
889
890        DependencyReport {
891            total_dependencies: total_deps,
892            optional_dependencies: optional_deps,
893            essential_dependencies: essential_deps,
894            heavy_dependencies: heavy_deps,
895            total_transitive_dependencies: total_transitive,
896            high_impact_recommendations,
897            recommendations: self.recommendations.clone(),
898            dependency_breakdown: self.generate_breakdown(),
899        }
900    }
901
902    /// Generate dependency breakdown by category
903    fn generate_breakdown(&self) -> HashMap<DependencyCategory, Vec<String>> {
904        let mut breakdown = HashMap::new();
905
906        for dep in self.dependencies.values() {
907            breakdown
908                .entry(dep.category)
909                .or_insert_with(Vec::new)
910                .push(dep.name.clone());
911        }
912
913        breakdown
914    }
915}
916
917impl Default for DependencyAudit {
918    fn default() -> Self {
919        Self::new()
920    }
921}
922
923// =============================================================================
924// Recommendations
925// =============================================================================
926
927/// Recommendation for dependency optimization
928#[derive(Debug, Clone)]
929pub struct DependencyRecommendation {
930    /// Name of the dependency
931    pub dependency: String,
932    /// Recommended action
933    pub action: RecommendationAction,
934    /// Reason for the recommendation
935    pub reason: String,
936    /// Impact of implementing the recommendation
937    pub impact: RecommendationImpact,
938    /// Effort required to implement
939    pub effort: ImplementationEffort,
940}
941
942/// Types of recommendations
943#[derive(Debug, Clone, PartialEq, Eq)]
944pub enum RecommendationAction {
945    /// Remove the dependency entirely
946    Remove,
947    /// Make the dependency optional/feature-gated
948    MakeOptional,
949    /// Use fewer features from the dependency
950    OptimizeFeatures,
951    /// Replace with a lighter alternative
952    ReplaceWithAlternative(String),
953    /// Consolidate multiple similar dependencies
954    ConsolidateAlternatives,
955    /// Update to a newer version
956    UpdateVersion,
957    /// Move to dev-dependencies
958    MoveToDevDeps,
959}
960
961/// Impact of implementing the recommendation
962#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
963pub enum RecommendationImpact {
964    /// Low impact on compile time/binary size
965    Low,
966    /// Medium impact
967    Medium,
968    /// High impact
969    High,
970    /// Very high impact
971    VeryHigh,
972}
973
974/// Effort required to implement the recommendation
975#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
976pub enum ImplementationEffort {
977    /// Low effort (configuration change)
978    Low,
979    /// Medium effort (some code changes)
980    Medium,
981    /// High effort (significant refactoring)
982    High,
983    /// Very high effort (major rewrite)
984    VeryHigh,
985}
986
987// =============================================================================
988// Audit Report
989// =============================================================================
990
991/// Comprehensive dependency audit report
992#[derive(Debug, Clone)]
993pub struct DependencyReport {
994    /// Total number of dependencies
995    pub total_dependencies: usize,
996    /// Number of optional dependencies
997    pub optional_dependencies: usize,
998    /// Number of essential dependencies
999    pub essential_dependencies: usize,
1000    /// Number of heavy dependencies
1001    pub heavy_dependencies: usize,
1002    /// Estimated total transitive dependencies
1003    pub total_transitive_dependencies: usize,
1004    /// Number of high-impact recommendations
1005    pub high_impact_recommendations: usize,
1006    /// All recommendations
1007    pub recommendations: Vec<DependencyRecommendation>,
1008    /// Breakdown by category
1009    pub dependency_breakdown: HashMap<DependencyCategory, Vec<String>>,
1010}
1011
1012impl DependencyReport {
1013    /// Generate a summary of the audit
1014    pub fn summary(&self) -> String {
1015        format!(
1016            "Dependency Audit Summary:\n\
1017            - Total dependencies: {}\n\
1018            - Essential: {}\n\
1019            - Optional: {}\n\
1020            - Heavy: {}\n\
1021            - Estimated transitive deps: {}\n\
1022            - High-impact recommendations: {}",
1023            self.total_dependencies,
1024            self.essential_dependencies,
1025            self.optional_dependencies,
1026            self.heavy_dependencies,
1027            self.total_transitive_dependencies,
1028            self.high_impact_recommendations
1029        )
1030    }
1031
1032    /// Generate detailed recommendations
1033    pub fn detailed_recommendations(&self) -> String {
1034        let mut output = String::new();
1035        output.push_str("Dependency Optimization Recommendations:\n\n");
1036
1037        for (i, rec) in self.recommendations.iter().enumerate() {
1038            output.push_str(&format!(
1039                "{}. {} ({})\n\
1040                   Action: {:?}\n\
1041                   Reason: {}\n\
1042                   Impact: {:?}, Effort: {:?}\n\n",
1043                i + 1,
1044                rec.dependency,
1045                match rec.impact {
1046                    RecommendationImpact::VeryHigh => "🔴 Very High",
1047                    RecommendationImpact::High => "🟡 High",
1048                    RecommendationImpact::Medium => "🟠 Medium",
1049                    RecommendationImpact::Low => "🟢 Low",
1050                },
1051                rec.action,
1052                rec.reason,
1053                rec.impact,
1054                rec.effort
1055            ));
1056        }
1057
1058        output
1059    }
1060
1061    /// Generate Cargo.toml optimizations
1062    pub fn generate_cargo_optimizations(&self) -> String {
1063        let mut optimizations = String::new();
1064        optimizations.push_str("# Recommended Cargo.toml optimizations:\n\n");
1065
1066        optimizations.push_str("# Feature-gate heavy dependencies:\n");
1067        optimizations.push_str("[dependencies]\n");
1068        optimizations.push_str("polars = { version = \"0.42\", optional = true, default-features = false, features = [\"lazy\"] }\n");
1069        optimizations
1070            .push_str("arrow = { version = \"53\", optional = true, default-features = false }\n");
1071        optimizations.push_str("arrow-ipc = { version = \"53\", optional = true }\n");
1072        optimizations.push_str("arrow-csv = { version = \"53\", optional = true }\n\n");
1073
1074        optimizations.push_str("# Minimize features for heavy dependencies:\n");
1075        optimizations.push_str("[features]\n");
1076        optimizations.push_str("dataframes = [\"polars\"]\n");
1077        optimizations.push_str("arrow = [\"dep:arrow\", \"dep:arrow-ipc\", \"dep:arrow-csv\"]\n");
1078        optimizations.push_str("full = [\"dataframes\", \"arrow\", \"serde\"]\n\n");
1079
1080        optimizations.push_str("# Profile optimizations:\n");
1081        optimizations.push_str("[profile.dev]\n");
1082        optimizations.push_str("opt-level = 1  # Faster dev builds\n\n");
1083
1084        optimizations.push_str("[profile.release]\n");
1085        optimizations.push_str("codegen-units = 1  # Better optimization\n");
1086        optimizations.push_str("lto = true  # Link-time optimization\n");
1087
1088        optimizations
1089    }
1090}
1091
1092// =============================================================================
1093// Utility Functions
1094// =============================================================================
1095
1096/// Calculate dependency tree metrics
1097pub fn calculate_metrics(audit: &DependencyAudit) -> DependencyMetrics {
1098    let deps = audit.dependencies();
1099
1100    let total_compile_time: u32 = deps
1101        .values()
1102        .map(|d| match d.compile_time_impact {
1103            CompileTimeImpact::Minimal => 1,
1104            CompileTimeImpact::Low => 3,
1105            CompileTimeImpact::Medium => 10,
1106            CompileTimeImpact::High => 20,
1107            CompileTimeImpact::VeryHigh => 40,
1108        })
1109        .sum();
1110
1111    let total_binary_size: u32 = deps
1112        .values()
1113        .map(|d| match d.binary_size_impact {
1114            BinarySizeImpact::Minimal => 1,
1115            BinarySizeImpact::Low => 5,
1116            BinarySizeImpact::Medium => 15,
1117            BinarySizeImpact::High => 50,
1118            BinarySizeImpact::VeryHigh => 200,
1119        })
1120        .sum();
1121
1122    DependencyMetrics {
1123        estimated_compile_time_seconds: total_compile_time,
1124        estimated_binary_size_mb: total_binary_size,
1125        dependency_depth: deps.values().map(|d| d.transitive_deps).max().unwrap_or(0),
1126        optimization_potential: audit.recommendations().len(),
1127    }
1128}
1129
1130/// Metrics about the dependency tree
1131#[derive(Debug, Clone)]
1132pub struct DependencyMetrics {
1133    /// Estimated total compile time in seconds
1134    pub estimated_compile_time_seconds: u32,
1135    /// Estimated binary size in MB
1136    pub estimated_binary_size_mb: u32,
1137    /// Maximum dependency depth
1138    pub dependency_depth: usize,
1139    /// Number of optimization opportunities
1140    pub optimization_potential: usize,
1141}
1142
1143/// Generate dependency visualization (simplified DOT format)
1144pub fn generate_dependency_graph(audit: &DependencyAudit) -> String {
1145    let mut graph = String::new();
1146    graph.push_str("digraph dependencies {\n");
1147    graph.push_str("  rankdir=TB;\n");
1148    graph.push_str("  node [shape=box];\n\n");
1149
1150    // Add nodes with colors based on category
1151    for dep in audit.dependencies().values() {
1152        let color = match dep.category {
1153            DependencyCategory::Essential => "lightblue",
1154            DependencyCategory::Optional => "lightgreen",
1155            DependencyCategory::Development => "lightyellow",
1156            DependencyCategory::Heavy => "lightcoral",
1157            DependencyCategory::Redundant => "lightgray",
1158            DependencyCategory::Security => "pink",
1159        };
1160
1161        graph.push_str(&format!(
1162            "  \"{}\" [fillcolor={}, style=filled];\n",
1163            dep.name, color
1164        ));
1165    }
1166
1167    graph.push_str("}\n");
1168    graph
1169}
1170
1171// =============================================================================
1172// Tests
1173// =============================================================================
1174
1175/// Automated dependency update checker and manager
1176#[derive(Debug)]
1177pub struct DependencyUpdater {
1178    config: UpdaterConfig,
1179    current_versions: HashMap<String, String>,
1180    latest_versions: HashMap<String, String>,
1181}
1182
1183/// Configuration for automated dependency updates
1184#[derive(Debug, Clone)]
1185pub struct UpdaterConfig {
1186    /// Allow major version updates (potentially breaking)
1187    pub allow_major_updates: bool,
1188    /// Allow minor version updates (backward compatible)
1189    pub allow_minor_updates: bool,
1190    /// Allow patch version updates (bug fixes only)
1191    pub allow_patch_updates: bool,
1192    /// Exclude specific packages from updates
1193    pub excluded_packages: HashSet<String>,
1194    /// Require specific version constraints for packages
1195    pub version_constraints: HashMap<String, String>,
1196    /// Check for security advisories
1197    pub check_security_advisories: bool,
1198}
1199
1200impl Default for UpdaterConfig {
1201    fn default() -> Self {
1202        Self {
1203            allow_major_updates: false,
1204            allow_minor_updates: true,
1205            allow_patch_updates: true,
1206            excluded_packages: HashSet::new(),
1207            version_constraints: HashMap::new(),
1208            check_security_advisories: true,
1209        }
1210    }
1211}
1212
1213/// Update recommendation for a dependency
1214#[derive(Debug, Clone)]
1215pub struct UpdateRecommendation {
1216    pub package_name: String,
1217    pub current_version: String,
1218    pub latest_version: String,
1219    pub update_type: UpdateType,
1220    pub security_advisory: Option<SecurityAdvisory>,
1221    pub breaking_changes: Vec<String>,
1222    pub priority: UpdatePriority,
1223}
1224
1225/// Type of version update
1226#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1227pub enum UpdateType {
1228    Major,
1229    Minor,
1230    Patch,
1231}
1232
1233/// Priority level for updates
1234#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
1235pub enum UpdatePriority {
1236    Critical, // Security fixes
1237    High,     // Bug fixes with significant impact
1238    Medium,   // Minor improvements
1239    Low,      // Optional updates
1240}
1241
1242/// Security advisory information
1243#[derive(Debug, Clone)]
1244pub struct SecurityAdvisory {
1245    pub id: String,
1246    pub title: String,
1247    pub severity: String,
1248    pub affected_versions: String,
1249    pub patched_versions: String,
1250    pub description: String,
1251}
1252
1253impl DependencyUpdater {
1254    /// Create a new dependency updater with default configuration
1255    pub fn new() -> Self {
1256        Self::with_config(UpdaterConfig::default())
1257    }
1258
1259    /// Create a new dependency updater with custom configuration
1260    pub fn with_config(config: UpdaterConfig) -> Self {
1261        Self {
1262            config,
1263            current_versions: HashMap::new(),
1264            latest_versions: HashMap::new(),
1265        }
1266    }
1267
1268    /// Check for available updates to dependencies
1269    pub fn check_for_updates(&mut self) -> Result<Vec<UpdateRecommendation>, String> {
1270        // Parse Cargo.toml to get current dependencies
1271        self.load_current_dependencies()?;
1272
1273        // Query registry for latest versions
1274        self.fetch_latest_versions()?;
1275
1276        // Generate update recommendations
1277        let mut recommendations = Vec::new();
1278
1279        for (package, current_version) in &self.current_versions {
1280            if self.config.excluded_packages.contains(package) {
1281                continue;
1282            }
1283
1284            if let Some(latest_version) = self.latest_versions.get(package) {
1285                if let Some(recommendation) =
1286                    self.analyze_update(package, current_version, latest_version)?
1287                {
1288                    recommendations.push(recommendation);
1289                }
1290            }
1291        }
1292
1293        // Sort by priority
1294        recommendations.sort_by(|a, b| b.priority.cmp(&a.priority));
1295
1296        Ok(recommendations)
1297    }
1298
1299    /// Generate automated update script
1300    pub fn generate_update_script(&self, recommendations: &[UpdateRecommendation]) -> String {
1301        let mut script = String::new();
1302        script.push_str("#!/bin/bash\n");
1303        script.push_str("# Automated dependency update script generated by sklears\n\n");
1304
1305        for rec in recommendations {
1306            if self.should_auto_update(rec) {
1307                script.push_str(&format!(
1308                    "echo \"Updating {} from {} to {}\"\n",
1309                    rec.package_name, rec.current_version, rec.latest_version
1310                ));
1311                script.push_str(&format!(
1312                    "cargo update -p {}:{}\n",
1313                    rec.package_name, rec.latest_version
1314                ));
1315            } else {
1316                script.push_str(&format!(
1317                    "# Manual review required for {}: {} -> {} ({})\n",
1318                    rec.package_name,
1319                    rec.current_version,
1320                    rec.latest_version,
1321                    match rec.update_type {
1322                        UpdateType::Major => "major version change",
1323                        UpdateType::Minor => "minor version change",
1324                        UpdateType::Patch => "patch version change",
1325                    }
1326                ));
1327            }
1328        }
1329
1330        script.push_str("\necho \"Update process complete. Running tests...\"\n");
1331        script.push_str("cargo test\n");
1332        script.push_str("cargo clippy -- -D warnings\n");
1333
1334        script
1335    }
1336
1337    /// Load current dependency versions from Cargo.toml
1338    fn load_current_dependencies(&mut self) -> Result<(), String> {
1339        // In a real implementation, this would parse Cargo.toml
1340        // For now, simulate with some common dependencies
1341        self.current_versions
1342            .insert("ndarray".to_string(), "0.15.6".to_string());
1343        self.current_versions
1344            .insert("serde".to_string(), "1.0.193".to_string());
1345        self.current_versions
1346            .insert("rayon".to_string(), "1.8.0".to_string());
1347        self.current_versions
1348            .insert("criterion".to_string(), "0.5.1".to_string());
1349        Ok(())
1350    }
1351
1352    /// Fetch latest versions from crates.io
1353    fn fetch_latest_versions(&mut self) -> Result<(), String> {
1354        // In a real implementation, this would query crates.io API
1355        // For now, simulate with some example versions
1356        self.latest_versions
1357            .insert("ndarray".to_string(), "0.15.7".to_string());
1358        self.latest_versions
1359            .insert("serde".to_string(), "1.0.195".to_string());
1360        self.latest_versions
1361            .insert("rayon".to_string(), "1.8.1".to_string());
1362        self.latest_versions
1363            .insert("criterion".to_string(), "0.5.1".to_string());
1364        Ok(())
1365    }
1366
1367    /// Analyze whether a package should be updated
1368    fn analyze_update(
1369        &self,
1370        package: &str,
1371        current: &str,
1372        latest: &str,
1373    ) -> Result<Option<UpdateRecommendation>, String> {
1374        if current == latest {
1375            return Ok(None);
1376        }
1377
1378        let update_type = self.determine_update_type(current, latest)?;
1379
1380        // Check if this type of update is allowed
1381        let allowed = match update_type {
1382            UpdateType::Major => self.config.allow_major_updates,
1383            UpdateType::Minor => self.config.allow_minor_updates,
1384            UpdateType::Patch => self.config.allow_patch_updates,
1385        };
1386
1387        if !allowed {
1388            return Ok(None);
1389        }
1390
1391        // Check for security advisories (simulated)
1392        let security_advisory = self.check_security_advisory(package, current);
1393
1394        let priority = if security_advisory.is_some() {
1395            UpdatePriority::Critical
1396        } else {
1397            match update_type {
1398                UpdateType::Major => UpdatePriority::Low,
1399                UpdateType::Minor => UpdatePriority::Medium,
1400                UpdateType::Patch => UpdatePriority::High,
1401            }
1402        };
1403
1404        Ok(Some(UpdateRecommendation {
1405            package_name: package.to_string(),
1406            current_version: current.to_string(),
1407            latest_version: latest.to_string(),
1408            update_type,
1409            security_advisory,
1410            breaking_changes: self.get_breaking_changes(package, current, latest),
1411            priority,
1412        }))
1413    }
1414
1415    /// Determine the type of version update
1416    fn determine_update_type(&self, current: &str, latest: &str) -> Result<UpdateType, String> {
1417        let current_parts: Vec<u32> = current.split('.').map(|s| s.parse().unwrap_or(0)).collect();
1418        let latest_parts: Vec<u32> = latest.split('.').map(|s| s.parse().unwrap_or(0)).collect();
1419
1420        if current_parts.len() < 3 || latest_parts.len() < 3 {
1421            return Err("Invalid version format".to_string());
1422        }
1423
1424        if latest_parts[0] > current_parts[0] {
1425            Ok(UpdateType::Major)
1426        } else if latest_parts[1] > current_parts[1] {
1427            Ok(UpdateType::Minor)
1428        } else {
1429            Ok(UpdateType::Patch)
1430        }
1431    }
1432
1433    /// Check for security advisories (simulated)
1434    fn check_security_advisory(&self, package: &str, version: &str) -> Option<SecurityAdvisory> {
1435        // In a real implementation, this would query RustSec advisory database
1436        // For demonstration, simulate an advisory for an old version
1437        if package == "serde" && version == "1.0.100" {
1438            Some(SecurityAdvisory {
1439                id: "RUSTSEC-2023-0001".to_string(),
1440                title: "Simulated security vulnerability".to_string(),
1441                severity: "Medium".to_string(),
1442                affected_versions: "< 1.0.150".to_string(),
1443                patched_versions: ">= 1.0.150".to_string(),
1444                description: "This is a simulated security advisory for demonstration".to_string(),
1445            })
1446        } else {
1447            None
1448        }
1449    }
1450
1451    /// Get breaking changes for a version update (simulated)
1452    fn get_breaking_changes(&self, _package: &str, _current: &str, _latest: &str) -> Vec<String> {
1453        // In a real implementation, this would parse changelogs or release notes
1454        Vec::new()
1455    }
1456
1457    /// Determine if an update should be applied automatically
1458    fn should_auto_update(&self, recommendation: &UpdateRecommendation) -> bool {
1459        match recommendation.update_type {
1460            UpdateType::Major => false, // Always require manual review for major updates
1461            UpdateType::Minor => recommendation.breaking_changes.is_empty(),
1462            UpdateType::Patch => true,
1463        }
1464    }
1465
1466    /// Generate a detailed update report
1467    pub fn generate_update_report(&self, recommendations: &[UpdateRecommendation]) -> String {
1468        let mut report = String::new();
1469        report.push_str("Dependency Update Report\n");
1470        report.push_str("========================\n\n");
1471
1472        let critical_count = recommendations
1473            .iter()
1474            .filter(|r| r.priority == UpdatePriority::Critical)
1475            .count();
1476        let high_count = recommendations
1477            .iter()
1478            .filter(|r| r.priority == UpdatePriority::High)
1479            .count();
1480        let medium_count = recommendations
1481            .iter()
1482            .filter(|r| r.priority == UpdatePriority::Medium)
1483            .count();
1484        let low_count = recommendations
1485            .iter()
1486            .filter(|r| r.priority == UpdatePriority::Low)
1487            .count();
1488
1489        report.push_str("Summary:\n");
1490        report.push_str(&format!("- Critical updates: {critical_count}\n"));
1491        report.push_str(&format!("- High priority updates: {high_count}\n"));
1492        report.push_str(&format!("- Medium priority updates: {medium_count}\n"));
1493        report.push_str(&format!("- Low priority updates: {low_count}\n\n"));
1494
1495        for rec in recommendations {
1496            report.push_str(&format!("Package: {}\n", rec.package_name));
1497            report.push_str(&format!("Current Version: {}\n", rec.current_version));
1498            report.push_str(&format!("Latest Version: {}\n", rec.latest_version));
1499            report.push_str(&format!("Update Type: {:?}\n", rec.update_type));
1500            report.push_str(&format!("Priority: {:?}\n", rec.priority));
1501
1502            if let Some(ref advisory) = rec.security_advisory {
1503                report.push_str(&format!(
1504                    "⚠️  Security Advisory: {} - {}\n",
1505                    advisory.id, advisory.title
1506                ));
1507                report.push_str(&format!("   Severity: {}\n", advisory.severity));
1508            }
1509
1510            if !rec.breaking_changes.is_empty() {
1511                report.push_str("Breaking Changes:\n");
1512                for change in &rec.breaking_changes {
1513                    report.push_str(&format!("   - {change}\n"));
1514                }
1515            }
1516
1517            report.push('\n');
1518        }
1519
1520        report
1521    }
1522}
1523
1524impl Default for DependencyUpdater {
1525    fn default() -> Self {
1526        Self::new()
1527    }
1528}
1529
1530#[allow(non_snake_case)]
1531#[cfg(test)]
1532mod tests {
1533    use super::*;
1534
1535    #[test]
1536    fn test_dependency_audit_creation() {
1537        let audit = DependencyAudit::new();
1538        assert!(!audit.dependencies().is_empty());
1539        assert!(!audit.recommendations().is_empty());
1540    }
1541
1542    #[test]
1543    fn test_dependency_categories() {
1544        let audit = DependencyAudit::new();
1545
1546        let essential = audit.dependencies_by_category(DependencyCategory::Essential);
1547        let optional = audit.dependencies_by_category(DependencyCategory::Optional);
1548        let heavy = audit.dependencies_by_category(DependencyCategory::Heavy);
1549
1550        assert!(!essential.is_empty());
1551        assert!(!optional.is_empty());
1552        assert!(!heavy.is_empty());
1553    }
1554
1555    #[test]
1556    fn test_report_generation() {
1557        let audit = DependencyAudit::new();
1558        let report = audit.generate_report();
1559
1560        assert!(report.total_dependencies > 0);
1561        assert!(report.essential_dependencies > 0);
1562        assert!(!report.summary().is_empty());
1563        assert!(!report.detailed_recommendations().is_empty());
1564    }
1565
1566    #[test]
1567    fn test_metrics_calculation() {
1568        let audit = DependencyAudit::new();
1569        let metrics = calculate_metrics(&audit);
1570
1571        assert!(metrics.estimated_compile_time_seconds > 0);
1572        assert!(metrics.estimated_binary_size_mb > 0);
1573        assert!(metrics.dependency_depth > 0);
1574    }
1575
1576    #[test]
1577    fn test_cargo_optimizations() {
1578        let audit = DependencyAudit::new();
1579        let report = audit.generate_report();
1580        let optimizations = report.generate_cargo_optimizations();
1581
1582        assert!(optimizations.contains("optional = true"));
1583        assert!(optimizations.contains("[features]"));
1584        assert!(optimizations.contains("[profile"));
1585    }
1586
1587    #[test]
1588    fn test_dependency_graph() {
1589        let audit = DependencyAudit::new();
1590        let graph = generate_dependency_graph(&audit);
1591
1592        assert!(graph.contains("digraph dependencies"));
1593        assert!(graph.contains("lightblue")); // Essential deps
1594        assert!(graph.contains("lightcoral")); // Heavy deps
1595    }
1596
1597    #[test]
1598    fn test_recommendations() {
1599        let audit = DependencyAudit::new();
1600        let recommendations = audit.recommendations();
1601
1602        // Should have recommendations for heavy dependencies
1603        assert!(recommendations
1604            .iter()
1605            .any(|r| matches!(r.action, RecommendationAction::MakeOptional)));
1606
1607        // Should have recommendations for redundant dependencies
1608        assert!(recommendations
1609            .iter()
1610            .any(|r| matches!(r.action, RecommendationAction::Remove)));
1611    }
1612
1613    #[test]
1614    fn test_license_checker() {
1615        let checker = LicenseChecker::new("Apache-2.0");
1616
1617        // Test compatible licenses
1618        assert_eq!(
1619            checker.check_compatibility("MIT"),
1620            LicenseCompatibility::Compatible
1621        );
1622        assert_eq!(
1623            checker.check_compatibility("BSD-3-Clause"),
1624            LicenseCompatibility::Compatible
1625        );
1626        assert_eq!(
1627            checker.check_compatibility("Apache-2.0"),
1628            LicenseCompatibility::Compatible
1629        );
1630
1631        // Test incompatible licenses
1632        assert_eq!(
1633            checker.check_compatibility("GPL-2.0"),
1634            LicenseCompatibility::Incompatible
1635        );
1636
1637        // Test licenses requiring attribution
1638        assert_eq!(
1639            checker.check_compatibility("LGPL-2.1"),
1640            LicenseCompatibility::CompatibleWithAttribution
1641        );
1642        assert_eq!(
1643            checker.check_compatibility("MPL-2.0"),
1644            LicenseCompatibility::CompatibleWithAttribution
1645        );
1646
1647        // Test copyleft compatible
1648        assert_eq!(
1649            checker.check_compatibility("GPL-3.0"),
1650            LicenseCompatibility::CompatibleCopyleft
1651        );
1652
1653        // Test unknown license
1654        assert_eq!(
1655            checker.check_compatibility("UNKNOWN-LICENSE"),
1656            LicenseCompatibility::Unknown
1657        );
1658
1659        // Test problematic licenses
1660        assert_eq!(
1661            checker.check_compatibility("AGPL-3.0"),
1662            LicenseCompatibility::ReviewRequired
1663        );
1664    }
1665
1666    #[test]
1667    fn test_license_report_generation() {
1668        let audit = DependencyAudit::new();
1669        let checker = LicenseChecker::new("Apache-2.0");
1670        let license_report = checker.generate_license_report(audit.dependencies());
1671
1672        // Should have dependencies in various categories
1673        assert!(!license_report.compatible.is_empty());
1674
1675        // Should detect any license issues
1676        let has_issues = license_report.has_issues();
1677
1678        // Generate reports
1679        let detailed = license_report.detailed_report();
1680        assert!(detailed.contains("License Compatibility Report"));
1681
1682        if has_issues {
1683            let summary = license_report.issue_summary();
1684            assert!(summary.contains("License Compatibility Issues"));
1685        }
1686    }
1687
1688    #[test]
1689    fn test_attribution_text_generation() {
1690        let audit = DependencyAudit::new();
1691        let checker = LicenseChecker::new("Apache-2.0");
1692        let attribution = checker.generate_attribution_text(audit.dependencies());
1693
1694        assert!(attribution.contains("Third-Party Licenses"));
1695        assert!(attribution.contains("This software includes"));
1696    }
1697
1698    #[test]
1699    fn test_mit_license_compatibility() {
1700        let checker = LicenseChecker::new("MIT");
1701
1702        // MIT should be compatible with most permissive licenses
1703        assert_eq!(
1704            checker.check_compatibility("MIT"),
1705            LicenseCompatibility::Compatible
1706        );
1707        assert_eq!(
1708            checker.check_compatibility("BSD-2-Clause"),
1709            LicenseCompatibility::Compatible
1710        );
1711        assert_eq!(
1712            checker.check_compatibility("Apache-2.0"),
1713            LicenseCompatibility::Compatible
1714        );
1715
1716        // But still incompatible with GPL-2.0
1717        assert_eq!(
1718            checker.check_compatibility("GPL-2.0"),
1719            LicenseCompatibility::Incompatible
1720        );
1721    }
1722
1723    #[test]
1724    fn test_dependency_updater_creation() {
1725        let updater = DependencyUpdater::new();
1726        assert_eq!(updater.config.allow_minor_updates, true);
1727        assert_eq!(updater.config.allow_major_updates, false);
1728        assert_eq!(updater.config.allow_patch_updates, true);
1729    }
1730
1731    #[test]
1732    fn test_update_recommendations() {
1733        let mut updater = DependencyUpdater::new();
1734        let recommendations = updater.check_for_updates().unwrap();
1735
1736        // Should have some recommendations due to simulated version differences
1737        assert!(!recommendations.is_empty());
1738
1739        // Should be sorted by priority (highest first)
1740        for i in 1..recommendations.len() {
1741            assert!(recommendations[i - 1].priority >= recommendations[i].priority);
1742        }
1743    }
1744
1745    #[test]
1746    fn test_update_script_generation() {
1747        let mut updater = DependencyUpdater::new();
1748        let recommendations = updater.check_for_updates().unwrap();
1749        let script = updater.generate_update_script(&recommendations);
1750
1751        assert!(script.contains("#!/bin/bash"));
1752        assert!(script.contains("cargo update"));
1753        assert!(script.contains("cargo test"));
1754    }
1755
1756    #[test]
1757    fn test_update_report_generation() {
1758        let mut updater = DependencyUpdater::new();
1759        let recommendations = updater.check_for_updates().unwrap();
1760        let report = updater.generate_update_report(&recommendations);
1761
1762        assert!(report.contains("Dependency Update Report"));
1763        assert!(report.contains("Summary:"));
1764        assert!(report.contains("Package:"));
1765    }
1766
1767    #[test]
1768    fn test_update_type_determination() {
1769        let updater = DependencyUpdater::new();
1770
1771        assert_eq!(
1772            updater.determine_update_type("1.0.0", "2.0.0").unwrap(),
1773            UpdateType::Major
1774        );
1775        assert_eq!(
1776            updater.determine_update_type("1.0.0", "1.1.0").unwrap(),
1777            UpdateType::Minor
1778        );
1779        assert_eq!(
1780            updater.determine_update_type("1.0.0", "1.0.1").unwrap(),
1781            UpdateType::Patch
1782        );
1783    }
1784
1785    #[test]
1786    fn test_gpl3_license_compatibility() {
1787        let checker = LicenseChecker::new("GPL-3.0");
1788
1789        // GPL-3.0 can incorporate most licenses
1790        assert_eq!(
1791            checker.check_compatibility("MIT"),
1792            LicenseCompatibility::Compatible
1793        );
1794        assert_eq!(
1795            checker.check_compatibility("Apache-2.0"),
1796            LicenseCompatibility::Compatible
1797        );
1798        assert_eq!(
1799            checker.check_compatibility("GPL-3.0"),
1800            LicenseCompatibility::Compatible
1801        );
1802
1803        // But not GPL-2.0
1804        assert_eq!(
1805            checker.check_compatibility("GPL-2.0"),
1806            LicenseCompatibility::Incompatible
1807        );
1808    }
1809}