#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Severity {
Error,
Warning,
Info,
}
impl Severity {
pub fn as_str(&self) -> &'static str {
match self {
Severity::Error => "error",
Severity::Warning => "warning",
Severity::Info => "info",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"error" => Some(Severity::Error),
"warning" => Some(Severity::Warning),
"info" => Some(Severity::Info),
_ => None,
}
}
pub fn rank(&self) -> u8 {
match self {
Severity::Info => 0,
Severity::Warning => 1,
Severity::Error => 2,
}
}
}
#[derive(Debug, Clone)]
pub struct Fix {
pub start: usize,
pub end: usize,
pub replacement: String,
pub description: String,
pub confidence: FixConfidence,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FixConfidence {
Safe,
Review,
}
impl FixConfidence {
pub fn as_str(&self) -> &'static str {
match self {
FixConfidence::Safe => "safe",
FixConfidence::Review => "review",
}
}
}
#[derive(Debug, Clone)]
pub struct Violation {
pub file: String,
pub line: u32,
pub column: Option<u32>,
pub rule_id: String,
pub severity: Severity,
pub message: String,
pub suggestion: Option<String>,
pub fix: Option<Fix>,
}
impl Violation {
pub fn sort_key(&self) -> (&str, u32, u8, u32, &str) {
let (bucket, col) = match self.column {
Some(c) => (1u8, c),
None => (0u8, 0),
};
(
self.file.as_str(),
self.line,
bucket,
col,
self.rule_id.as_str(),
)
}
}
pub fn sort_violations(violations: &mut [Violation]) {
violations.sort_by(|a, b| a.sort_key().cmp(&b.sort_key()));
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CategoryStatus {
Pass,
Fail,
Skipped,
}
impl CategoryStatus {
pub fn as_str(&self) -> &'static str {
match self {
CategoryStatus::Pass => "PASS",
CategoryStatus::Fail => "FAIL",
CategoryStatus::Skipped => "SKIPPED",
}
}
}
#[derive(Debug, Clone)]
pub struct CategorySummary {
pub category_id: String,
pub title: String,
pub status: CategoryStatus,
pub rules_applied: u32,
pub rules_passed: u32,
pub violations: Vec<Violation>,
}
impl CategorySummary {
pub fn build(
category_id: impl Into<String>,
title: impl Into<String>,
rules_applied: u32,
rules_passed: u32,
violations: Vec<Violation>,
) -> Self {
let status = if rules_applied == 0 {
CategoryStatus::Skipped
} else if !violations.is_empty() {
CategoryStatus::Fail
} else {
CategoryStatus::Pass
};
Self {
category_id: category_id.into(),
title: title.into(),
status,
rules_applied,
rules_passed,
violations,
}
}
}
#[derive(Debug, Clone)]
pub struct SkippedRule {
pub rule_id: String,
pub reason: String,
}
#[derive(Debug, Clone)]
pub struct ComplianceReport {
pub tool_version: String,
pub journal_id: String,
pub violations: Vec<Violation>,
pub categories: Vec<CategorySummary>,
pub compliance_percentage: Option<f64>,
pub skipped_rules: Vec<SkippedRule>,
}