use crate::error::Result;
use serde::{Deserialize, Serialize};
use std::path::Path;
use std::time::SystemTime;
#[derive(Debug)]
pub struct ContributionChecker {
#[allow(dead_code)]
config: ContributionConfig,
quality_gates: Vec<QualityGate>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContributionConfig {
pub min_coverage: f64,
pub max_complexity: u32,
pub require_docs: bool,
pub require_property_tests: bool,
pub max_line_length: usize,
pub clippy_compliance: ClippyLevel,
pub performance_threshold: f64,
}
impl Default for ContributionConfig {
fn default() -> Self {
Self {
min_coverage: 90.0,
max_complexity: 10,
require_docs: true,
require_property_tests: true,
max_line_length: 100,
clippy_compliance: ClippyLevel::AllowedByDefault,
performance_threshold: 0.05, }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ClippyLevel {
AllowedByDefault,
Warn,
Deny,
Forbid,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ReviewCriteria {
pub algorithmic_correctness: AlgorithmicCriteria,
pub code_quality: CodeQualityCriteria,
pub documentation: DocumentationCriteria,
pub testing: TestingCriteria,
pub performance: PerformanceCriteria,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AlgorithmicCriteria {
pub mathematical_soundness: bool,
pub convergence_guarantees: bool,
pub numerical_stability: bool,
pub edge_case_handling: bool,
pub reference_validation: bool,
}
impl Default for AlgorithmicCriteria {
fn default() -> Self {
Self {
mathematical_soundness: true,
convergence_guarantees: true,
numerical_stability: true,
edge_case_handling: true,
reference_validation: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CodeQualityCriteria {
pub type_safety: bool,
pub memory_efficiency: bool,
pub error_handling: bool,
pub api_consistency: bool,
pub rust_idioms: bool,
}
impl Default for CodeQualityCriteria {
fn default() -> Self {
Self {
type_safety: true,
memory_efficiency: true,
error_handling: true,
api_consistency: true,
rust_idioms: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentationCriteria {
pub api_docs: bool,
pub examples: bool,
pub mathematical_background: bool,
pub performance_docs: bool,
pub usage_patterns: bool,
}
impl Default for DocumentationCriteria {
fn default() -> Self {
Self {
api_docs: true,
examples: true,
mathematical_background: true,
performance_docs: true,
usage_patterns: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TestingCriteria {
pub unit_tests: bool,
pub integration_tests: bool,
pub property_tests: bool,
pub edge_case_tests: bool,
pub benchmarks: bool,
pub regression_tests: bool,
}
impl Default for TestingCriteria {
fn default() -> Self {
Self {
unit_tests: true,
integration_tests: true,
property_tests: true,
edge_case_tests: true,
benchmarks: true,
regression_tests: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceCriteria {
pub baseline_comparison: bool,
pub memory_analysis: bool,
pub scalability: bool,
pub parallelization: bool,
pub complexity_analysis: bool,
}
impl Default for PerformanceCriteria {
fn default() -> Self {
Self {
baseline_comparison: true,
memory_analysis: true,
scalability: true,
parallelization: true,
complexity_analysis: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityGate {
pub name: String,
pub description: String,
pub gate_type: QualityGateType,
pub threshold: f64,
pub blocking: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum QualityGateType {
CodeCoverage,
TestPassing,
LintCompliance,
DocumentationCoverage,
PerformanceRegression,
SecurityVulnerabilities,
DependencyLicenses,
APIBreaking,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContributionResult {
pub overall_score: f64,
pub gate_results: Vec<GateResult>,
pub recommendations: Vec<String>,
pub blocking_issues: Vec<String>,
pub timestamp: SystemTime,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GateResult {
pub gate_name: String,
pub passed: bool,
pub score: f64,
pub details: String,
pub improvement_suggestions: Vec<String>,
}
impl ContributionChecker {
pub fn new() -> Self {
Self::with_config(ContributionConfig::default())
}
pub fn with_config(config: ContributionConfig) -> Self {
let quality_gates = Self::create_default_quality_gates(&config);
Self {
config,
quality_gates,
}
}
pub fn check_contribution(
&self,
path: impl AsRef<Path>,
criteria: &ReviewCriteria,
) -> Result<ContributionResult> {
let path = path.as_ref();
let mut gate_results = Vec::new();
let mut blocking_issues = Vec::new();
let mut recommendations = Vec::new();
for gate in &self.quality_gates {
let result = self.run_quality_gate(gate, path, criteria)?;
if gate.blocking && !result.passed {
blocking_issues.push(format!("Blocking issue: {}", result.details));
}
recommendations.extend(result.improvement_suggestions.clone());
gate_results.push(result);
}
let overall_score = if gate_results.is_empty() {
0.0
} else {
gate_results.iter().map(|r| r.score).sum::<f64>() / gate_results.len() as f64
};
Ok(ContributionResult {
overall_score,
gate_results,
recommendations,
blocking_issues,
timestamp: SystemTime::now(),
})
}
fn run_quality_gate(
&self,
gate: &QualityGate,
path: &Path,
_criteria: &ReviewCriteria,
) -> Result<GateResult> {
match gate.gate_type {
QualityGateType::CodeCoverage => self.check_code_coverage(gate, path),
QualityGateType::TestPassing => self.check_test_passing(gate, path),
QualityGateType::LintCompliance => self.check_lint_compliance(gate, path),
QualityGateType::DocumentationCoverage => self.check_documentation_coverage(gate, path),
QualityGateType::PerformanceRegression => self.check_performance_regression(gate, path),
QualityGateType::SecurityVulnerabilities => {
self.check_security_vulnerabilities(gate, path)
}
QualityGateType::DependencyLicenses => self.check_dependency_licenses(gate, path),
QualityGateType::APIBreaking => self.check_api_breaking(gate, path),
}
}
fn check_code_coverage(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
let coverage = 85.0; let passed = coverage >= gate.threshold;
Ok(GateResult {
gate_name: gate.name.clone(),
passed,
score: if passed { 100.0 } else { coverage },
details: format!(
"Code coverage: {:.1}% (threshold: {:.1}%)",
coverage, gate.threshold
),
improvement_suggestions: if passed {
vec![]
} else {
vec![
"Add more unit tests for uncovered code paths".to_string(),
"Consider adding property-based tests for algorithmic code".to_string(),
"Add integration tests for end-to-end workflows".to_string(),
]
},
})
}
fn check_test_passing(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
let all_tests_pass = true; let score = if all_tests_pass { 100.0 } else { 0.0 };
Ok(GateResult {
gate_name: gate.name.clone(),
passed: all_tests_pass,
score,
details: if all_tests_pass {
"All tests passing".to_string()
} else {
"Some tests failing".to_string()
},
improvement_suggestions: if all_tests_pass {
vec![]
} else {
vec![
"Fix failing tests before submitting".to_string(),
"Ensure tests are deterministic and reproducible".to_string(),
]
},
})
}
fn check_lint_compliance(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
let lint_score = 95.0; let passed = lint_score >= gate.threshold;
Ok(GateResult {
gate_name: gate.name.clone(),
passed,
score: lint_score,
details: format!("Lint compliance: {lint_score:.1}%"),
improvement_suggestions: if passed {
vec![]
} else {
vec![
"Fix clippy warnings and errors".to_string(),
"Follow Rust naming conventions".to_string(),
"Remove unused imports and variables".to_string(),
]
},
})
}
fn check_documentation_coverage(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
let doc_coverage = 88.0; let passed = doc_coverage >= gate.threshold;
Ok(GateResult {
gate_name: gate.name.clone(),
passed,
score: doc_coverage,
details: format!("Documentation coverage: {doc_coverage:.1}%"),
improvement_suggestions: if passed {
vec![]
} else {
vec![
"Add doc comments for all public APIs".to_string(),
"Include executable examples in documentation".to_string(),
"Document mathematical foundations and algorithms".to_string(),
"Add performance characteristics documentation".to_string(),
]
},
})
}
fn check_performance_regression(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
let performance_change: f64 = -0.02; let passed = performance_change.abs() <= gate.threshold;
Ok(GateResult {
gate_name: gate.name.clone(),
passed,
score: if passed { 100.0 } else { 50.0 },
details: format!(
"Performance change: {:.1}% (threshold: ±{:.1}%)",
performance_change * 100.0,
gate.threshold * 100.0
),
improvement_suggestions: if passed {
vec![]
} else {
vec![
"Profile the code to identify performance bottlenecks".to_string(),
"Consider algorithmic optimizations".to_string(),
"Add benchmarks to track performance over time".to_string(),
]
},
})
}
fn check_security_vulnerabilities(
&self,
gate: &QualityGate,
_path: &Path,
) -> Result<GateResult> {
let vulnerabilities_found = 0; let passed = vulnerabilities_found == 0;
Ok(GateResult {
gate_name: gate.name.clone(),
passed,
score: if passed { 100.0 } else { 0.0 },
details: format!("Security vulnerabilities found: {vulnerabilities_found}"),
improvement_suggestions: if passed {
vec![]
} else {
vec![
"Update dependencies with known vulnerabilities".to_string(),
"Review unsafe code blocks for safety".to_string(),
"Validate all external inputs".to_string(),
]
},
})
}
fn check_dependency_licenses(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
let license_compatible = true;
Ok(GateResult {
gate_name: gate.name.clone(),
passed: license_compatible,
score: if license_compatible { 100.0 } else { 0.0 },
details: if license_compatible {
"All dependencies have compatible licenses".to_string()
} else {
"Some dependencies have incompatible licenses".to_string()
},
improvement_suggestions: if license_compatible {
vec![]
} else {
vec![
"Replace dependencies with incompatible licenses".to_string(),
"Ensure all licenses are compatible with project license".to_string(),
]
},
})
}
fn check_api_breaking(&self, gate: &QualityGate, _path: &Path) -> Result<GateResult> {
let breaking_changes = false;
Ok(GateResult {
gate_name: gate.name.clone(),
passed: !breaking_changes,
score: if breaking_changes { 0.0 } else { 100.0 },
details: if breaking_changes {
"Breaking API changes detected".to_string()
} else {
"No breaking API changes detected".to_string()
},
improvement_suggestions: if breaking_changes {
vec![
"Consider deprecation warnings before removing APIs".to_string(),
"Use semantic versioning for breaking changes".to_string(),
"Provide migration guides for API changes".to_string(),
]
} else {
vec![]
},
})
}
fn create_default_quality_gates(config: &ContributionConfig) -> Vec<QualityGate> {
vec![
QualityGate {
name: "Code Coverage".to_string(),
description: "Minimum code coverage percentage".to_string(),
gate_type: QualityGateType::CodeCoverage,
threshold: config.min_coverage,
blocking: true,
},
QualityGate {
name: "Test Passing".to_string(),
description: "All tests must pass".to_string(),
gate_type: QualityGateType::TestPassing,
threshold: 100.0,
blocking: true,
},
QualityGate {
name: "Lint Compliance".to_string(),
description: "Code must pass linting checks".to_string(),
gate_type: QualityGateType::LintCompliance,
threshold: 95.0,
blocking: true,
},
QualityGate {
name: "Documentation Coverage".to_string(),
description: "Minimum documentation coverage".to_string(),
gate_type: QualityGateType::DocumentationCoverage,
threshold: 85.0,
blocking: false,
},
QualityGate {
name: "Performance Regression".to_string(),
description: "No significant performance regression".to_string(),
gate_type: QualityGateType::PerformanceRegression,
threshold: config.performance_threshold,
blocking: false,
},
QualityGate {
name: "Security Vulnerabilities".to_string(),
description: "No security vulnerabilities".to_string(),
gate_type: QualityGateType::SecurityVulnerabilities,
threshold: 0.0,
blocking: true,
},
QualityGate {
name: "Dependency Licenses".to_string(),
description: "All dependencies have compatible licenses".to_string(),
gate_type: QualityGateType::DependencyLicenses,
threshold: 100.0,
blocking: true,
},
QualityGate {
name: "API Breaking Changes".to_string(),
description: "No breaking API changes without version bump".to_string(),
gate_type: QualityGateType::APIBreaking,
threshold: 0.0,
blocking: true,
},
]
}
}
impl Default for ContributionChecker {
fn default() -> Self {
Self::new()
}
}
impl ContributionResult {
pub fn quality_score(&self) -> f64 {
self.overall_score
}
pub fn meets_requirements(&self) -> bool {
self.blocking_issues.is_empty()
}
pub fn recommendations(&self) -> &[String] {
&self.recommendations
}
pub fn generate_report(&self) -> String {
let mut report = String::new();
report.push_str("# Contribution Review Report\n\n");
report.push_str(&format!(
"**Overall Score**: {:.1}/100\n",
self.overall_score
));
report.push_str(&format!(
"**Status**: {}\n\n",
if self.meets_requirements() {
"✅ APPROVED"
} else {
"❌ NEEDS WORK"
}
));
if !self.blocking_issues.is_empty() {
report.push_str("## 🚫 Blocking Issues\n\n");
for issue in &self.blocking_issues {
report.push_str(&format!("- {issue}\n"));
}
report.push('\n');
}
report.push_str("## 📊 Quality Gate Results\n\n");
for result in &self.gate_results {
let status = if result.passed { "✅" } else { "❌" };
report.push_str(&format!(
"### {} {} ({:.1}/100)\n",
status, result.gate_name, result.score
));
report.push_str(&format!("{}\n\n", result.details));
if !result.improvement_suggestions.is_empty() {
report.push_str("**Suggestions:**\n");
for suggestion in &result.improvement_suggestions {
report.push_str(&format!("- {suggestion}\n"));
}
report.push('\n');
}
}
if !self.recommendations.is_empty() {
report.push_str("## 💡 General Recommendations\n\n");
for recommendation in &self.recommendations {
report.push_str(&format!("- {recommendation}\n"));
}
}
report
}
}
pub struct ContributionWorkflow {
steps: Vec<WorkflowStep>,
}
#[derive(Debug, Clone)]
pub struct WorkflowStep {
pub name: String,
pub description: String,
pub commands: Vec<String>,
pub automated: bool,
}
impl ContributionWorkflow {
pub fn standard() -> Self {
let steps = vec![
WorkflowStep {
name: "Fork and Clone".to_string(),
description: "Fork the repository and clone your fork".to_string(),
commands: vec![
"# Fork on GitHub".to_string(),
"git clone https://github.com/cool-japan/sklears.git".to_string(),
"cd sklears".to_string(),
"git remote add upstream https://github.com/cool-japan/sklears.git".to_string(),
],
automated: false,
},
WorkflowStep {
name: "Create Feature Branch".to_string(),
description: "Create a new branch for your feature".to_string(),
commands: vec!["git checkout -b feature/your-feature-name".to_string()],
automated: false,
},
WorkflowStep {
name: "Development".to_string(),
description: "Implement your changes following best practices".to_string(),
commands: vec![
"# Make your changes".to_string(),
"cargo fmt".to_string(),
"cargo clippy -- -D warnings".to_string(),
],
automated: false,
},
WorkflowStep {
name: "Testing".to_string(),
description: "Run comprehensive tests".to_string(),
commands: vec![
"cargo nextest run --no-fail-fast".to_string(),
"cargo test --doc".to_string(),
"cargo bench".to_string(),
],
automated: true,
},
WorkflowStep {
name: "Documentation".to_string(),
description: "Update documentation".to_string(),
commands: vec![
"cargo doc --no-deps".to_string(),
"# Update CHANGELOG.md".to_string(),
"# Update relevant README files".to_string(),
],
automated: false,
},
WorkflowStep {
name: "Quality Checks".to_string(),
description: "Run quality gates".to_string(),
commands: vec![
"cargo audit".to_string(),
"cargo deny check".to_string(),
"# Run contribution checker".to_string(),
],
automated: true,
},
WorkflowStep {
name: "Commit and Push".to_string(),
description: "Commit changes with descriptive message".to_string(),
commands: vec![
"git add .".to_string(),
"git commit -m \"feat: add your feature description\"".to_string(),
"git push origin feature/your-feature-name".to_string(),
],
automated: false,
},
WorkflowStep {
name: "Pull Request".to_string(),
description: "Create pull request for review".to_string(),
commands: vec![
"# Create PR on GitHub".to_string(),
"# Fill out PR template".to_string(),
"# Request reviews".to_string(),
],
automated: false,
},
];
Self { steps }
}
pub fn steps(&self) -> &[WorkflowStep] {
&self.steps
}
pub fn generate_guide(&self) -> String {
let mut guide = String::new();
guide.push_str("# Contribution Workflow Guide\n\n");
guide.push_str("Follow these steps to contribute to sklears:\n\n");
for (i, step) in self.steps.iter().enumerate() {
guide.push_str(&format!("## {}. {}\n\n", i + 1, step.name));
guide.push_str(&format!("{}\n\n", step.description));
if !step.commands.is_empty() {
guide.push_str("```bash\n");
for command in &step.commands {
guide.push_str(&format!("{command}\n"));
}
guide.push_str("```\n\n");
}
if step.automated {
guide.push_str("*This step can be automated in CI/CD.*\n\n");
}
}
guide
}
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_contribution_checker_creation() {
let checker = ContributionChecker::new();
assert_eq!(checker.config.min_coverage, 90.0);
assert!(!checker.quality_gates.is_empty());
}
#[test]
fn test_review_criteria_default() {
let criteria = ReviewCriteria::default();
assert!(criteria.algorithmic_correctness.mathematical_soundness);
assert!(criteria.code_quality.type_safety);
assert!(criteria.documentation.api_docs);
assert!(criteria.testing.unit_tests);
assert!(criteria.performance.baseline_comparison);
}
#[test]
fn test_contribution_workflow() {
let workflow = ContributionWorkflow::standard();
assert!(!workflow.steps().is_empty());
let guide = workflow.generate_guide();
assert!(guide.contains("Contribution Workflow Guide"));
assert!(guide.contains("Fork and Clone"));
}
#[test]
fn test_quality_gate_types() {
let config = ContributionConfig::default();
let gates = ContributionChecker::create_default_quality_gates(&config);
let gate_types: Vec<_> = gates.iter().map(|g| &g.gate_type).collect();
assert!(gate_types
.iter()
.any(|t| matches!(t, QualityGateType::CodeCoverage)));
assert!(gate_types
.iter()
.any(|t| matches!(t, QualityGateType::TestPassing)));
assert!(gate_types
.iter()
.any(|t| matches!(t, QualityGateType::LintCompliance)));
}
#[test]
fn test_contribution_result() {
let result = ContributionResult {
overall_score: 85.5,
gate_results: vec![GateResult {
gate_name: "Test".to_string(),
passed: true,
score: 100.0,
details: "All tests pass".to_string(),
improvement_suggestions: vec![],
}],
recommendations: vec!["Improve documentation".to_string()],
blocking_issues: vec![],
timestamp: SystemTime::now(),
};
assert_eq!(result.quality_score(), 85.5);
assert!(result.meets_requirements());
assert_eq!(result.recommendations().len(), 1);
let report = result.generate_report();
assert!(report.contains("Contribution Review Report"));
assert!(report.contains("✅ APPROVED"));
}
}