#![allow(dead_code, unused_imports, unused_variables)]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum QaProfile {
#[default]
Standard,
Strict,
Minimal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum QualityGrade {
S,
A,
B,
C,
D,
F,
}
impl QualityGrade {
pub fn from_score(score: f64) -> Self {
if score >= 95.0 {
Self::S
} else if score >= 90.0 {
Self::A
} else if score >= 80.0 {
Self::B
} else if score >= 70.0 {
Self::C
} else if score >= 60.0 {
Self::D
} else {
Self::F
}
}
}
impl std::fmt::Display for QualityGrade {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::S => write!(f, "S"),
Self::A => write!(f, "A"),
Self::B => write!(f, "B"),
Self::C => write!(f, "C"),
Self::D => write!(f, "D"),
Self::F => write!(f, "F"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QaWeights {
pub syntax: f64,
pub format: f64,
pub lint: f64,
pub type_check: f64,
pub test: f64,
pub security: f64,
}
impl QaWeights {
pub fn standard() -> Self {
Self {
syntax: 10.0,
format: 5.0,
lint: 15.0,
type_check: 10.0,
test: 30.0,
security: 10.0,
}
}
pub fn strict() -> Self {
Self {
syntax: 10.0,
format: 10.0,
lint: 20.0,
type_check: 15.0,
test: 30.0,
security: 15.0,
}
}
pub fn minimal() -> Self {
Self {
syntax: 40.0,
format: 0.0,
lint: 0.0,
type_check: 40.0,
test: 0.0,
security: 0.0,
}
}
pub fn total(&self) -> f64 {
self.syntax + self.format + self.lint + self.type_check + self.test + self.security
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QaConfig {
#[serde(default)]
pub profile: QaProfile,
#[serde(default = "default_auto_fix_iterations")]
pub auto_fix_iterations: u32,
#[serde(default = "default_test_retry_iterations")]
pub test_retry_iterations: u32,
}
impl QaConfig {
pub fn from_schema(path: Option<&std::path::Path>, profile_name: &str) -> anyhow::Result<Self> {
let schema = crate::templates::load_qa_schema_profile(path, profile_name)?;
let profile = match profile_name {
"strict" => QaProfile::Strict,
"minimal" => QaProfile::Minimal,
_ => QaProfile::Standard,
};
let auto_fix = schema
.qa_profile
.feedback_loops
.as_ref()
.and_then(|fl| fl.auto_fix.as_ref())
.and_then(|af| af.get("max_iterations"))
.and_then(|v| v.as_u64())
.unwrap_or(default_auto_fix_iterations() as u64) as u32;
let test_retry = schema
.qa_profile
.feedback_loops
.as_ref()
.and_then(|fl| fl.retry_with_context.as_ref())
.and_then(|rc| rc.get("max_iterations"))
.and_then(|v| v.as_u64())
.unwrap_or(default_test_retry_iterations() as u64) as u32;
Ok(Self {
profile,
auto_fix_iterations: auto_fix,
test_retry_iterations: test_retry,
})
}
}
impl QaWeights {
pub fn from_schema(path: Option<&std::path::Path>, profile_name: &str) -> anyhow::Result<Self> {
let schema = crate::templates::load_qa_schema_profile(path, profile_name)?;
Ok(crate::templates::qa_schema_to_weights(&schema))
}
}
fn default_auto_fix_iterations() -> u32 {
3
}
fn default_test_retry_iterations() -> u32 {
2
}
impl Default for QaConfig {
fn default() -> Self {
Self {
profile: QaProfile::Standard,
auto_fix_iterations: default_auto_fix_iterations(),
test_retry_iterations: default_test_retry_iterations(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QaReport {
pub language: String,
pub profile: QaProfile,
pub stages: Vec<QaStageResult>,
pub score: f64,
pub grade: QualityGrade,
pub total_duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QaStageResult {
pub stage: QaStage,
pub passed: bool,
pub duration_ms: u64,
pub output: String,
pub error_count: usize,
pub warning_count: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum QaStage {
Syntax,
Format,
Lint,
TypeCheck,
Test,
Security,
}
impl std::fmt::Display for QaStage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Syntax => write!(f, "Syntax"),
Self::Format => write!(f, "Format"),
Self::Lint => write!(f, "Lint"),
Self::TypeCheck => write!(f, "TypeCheck"),
Self::Test => write!(f, "Test"),
Self::Security => write!(f, "Security"),
}
}
}
pub fn compute_score(stages: &[QaStageResult], weights: &QaWeights) -> f64 {
let total_weight = weights.total();
if total_weight <= 0.0 {
return 100.0;
}
let mut earned = 0.0;
for stage in stages {
let weight = match stage.stage {
QaStage::Syntax => weights.syntax,
QaStage::Format => weights.format,
QaStage::Lint => weights.lint,
QaStage::TypeCheck => weights.type_check,
QaStage::Test => weights.test,
QaStage::Security => weights.security,
};
if stage.passed {
earned += weight;
} else if stage.warning_count > 0 && stage.error_count == 0 {
earned += weight * 0.5;
}
}
(earned / total_weight * 100.0).min(100.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quality_grade_from_score() {
assert_eq!(QualityGrade::from_score(100.0), QualityGrade::S);
assert_eq!(QualityGrade::from_score(95.0), QualityGrade::S);
assert_eq!(QualityGrade::from_score(94.9), QualityGrade::A);
assert_eq!(QualityGrade::from_score(90.0), QualityGrade::A);
assert_eq!(QualityGrade::from_score(80.0), QualityGrade::B);
assert_eq!(QualityGrade::from_score(70.0), QualityGrade::C);
assert_eq!(QualityGrade::from_score(60.0), QualityGrade::D);
assert_eq!(QualityGrade::from_score(59.9), QualityGrade::F);
}
#[test]
fn test_compute_score_all_pass() {
let stages = vec![
QaStageResult {
stage: QaStage::Syntax,
passed: true,
duration_ms: 100,
output: String::new(),
error_count: 0,
warning_count: 0,
},
QaStageResult {
stage: QaStage::Test,
passed: true,
duration_ms: 5000,
output: String::new(),
error_count: 0,
warning_count: 0,
},
];
let weights = QaWeights::standard();
let score = compute_score(&stages, &weights);
assert!(score > 0.0);
}
#[test]
fn test_compute_score_all_fail() {
let stages = vec![QaStageResult {
stage: QaStage::Syntax,
passed: false,
duration_ms: 100,
output: "error".to_string(),
error_count: 1,
warning_count: 0,
}];
let weights = QaWeights::standard();
let score = compute_score(&stages, &weights);
assert_eq!(score, 0.0);
}
#[test]
fn test_qa_profile_default() {
let config = QaConfig::default();
assert_eq!(config.profile, QaProfile::Standard);
assert_eq!(config.auto_fix_iterations, 3);
}
}