use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct ComplexityMetrics {
#[serde(default)]
pub cyclomatic: u32,
#[serde(default)]
pub cognitive: u32,
#[serde(default)]
pub grade: ComplexityGrade,
#[serde(default)]
pub smells: Vec<CodeSmell>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum ComplexityGrade {
#[default]
A,
B,
C,
D,
F,
}
impl fmt::Display for ComplexityGrade {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
ComplexityGrade::A => "A",
ComplexityGrade::B => "B",
ComplexityGrade::C => "C",
ComplexityGrade::D => "D",
ComplexityGrade::F => "F",
};
f.write_str(s)
}
}
impl ComplexityGrade {
pub fn from_cyclomatic(v: u32) -> Self {
match v {
0..=5 => Self::A,
6..=10 => Self::B,
11..=15 => Self::C,
16..=20 => Self::D,
_ => Self::F,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum CodeSmell {
LongFunction { lines: usize },
DeepNesting { max_depth: u8 },
TooManyParams { count: usize },
MissingDocstring,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn grade_from_cyclomatic_thresholds() {
assert_eq!(ComplexityGrade::from_cyclomatic(0), ComplexityGrade::A);
assert_eq!(ComplexityGrade::from_cyclomatic(5), ComplexityGrade::A);
assert_eq!(ComplexityGrade::from_cyclomatic(6), ComplexityGrade::B);
assert_eq!(ComplexityGrade::from_cyclomatic(11), ComplexityGrade::C);
assert_eq!(ComplexityGrade::from_cyclomatic(16), ComplexityGrade::D);
assert_eq!(ComplexityGrade::from_cyclomatic(50), ComplexityGrade::F);
}
#[test]
fn grade_display_is_single_letter() {
assert_eq!(ComplexityGrade::A.to_string(), "A");
assert_eq!(ComplexityGrade::B.to_string(), "B");
assert_eq!(ComplexityGrade::C.to_string(), "C");
assert_eq!(ComplexityGrade::D.to_string(), "D");
assert_eq!(ComplexityGrade::F.to_string(), "F");
}
#[test]
fn grade_orders_a_through_f() {
assert!(ComplexityGrade::A < ComplexityGrade::B);
assert!(ComplexityGrade::B < ComplexityGrade::C);
assert!(ComplexityGrade::C < ComplexityGrade::D);
assert!(ComplexityGrade::D < ComplexityGrade::F);
}
#[test]
fn metrics_round_trip_json() {
let m = ComplexityMetrics {
cyclomatic: 7,
cognitive: 12,
grade: ComplexityGrade::B,
smells: vec![CodeSmell::LongFunction { lines: 80 }],
};
let s = serde_json::to_string(&m).unwrap();
let back: ComplexityMetrics = serde_json::from_str(&s).unwrap();
assert_eq!(m, back);
}
#[test]
fn metrics_default_when_field_missing() {
let s = r#"{}"#;
let m: ComplexityMetrics = serde_json::from_str(s).unwrap();
assert_eq!(m, ComplexityMetrics::default());
}
}