use serde::Serialize;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub enum Severity {
Fix,
Warn,
Info,
}
impl Severity {
#[must_use]
pub fn label(self) -> &'static str {
match self {
Severity::Fix => "FIX",
Severity::Warn => "WARN",
Severity::Info => "INFO",
}
}
}
impl std::fmt::Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.label())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum Category {
Linker,
Profile,
Dependencies,
ProcMacros,
BuildScripts,
Features,
DevDeps,
Toolchain,
Workspace,
Incremental,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
pub enum Impact {
High,
Medium,
Low,
}
impl Impact {
#[must_use]
pub fn label(self) -> &'static str {
match self {
Impact::High => "High",
Impact::Medium => "Medium",
Impact::Low => "Low",
}
}
}
impl std::fmt::Display for Impact {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.label())
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Finding {
pub severity: Severity,
pub category: Category,
pub impact: Impact,
pub title: String,
pub description: String,
pub fix: Option<Fix>,
}
#[derive(Debug, Clone, Serialize)]
pub struct Fix {
pub description: String,
pub kind: FixKind,
}
#[derive(Debug, Clone, Serialize)]
pub enum FixKind {
CargoConfig {
key_path: String,
value: String,
},
CargoToml {
section: String,
key: String,
value: String,
},
ShellCommand(String),
Manual(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn severity_orders_fix_before_warn_before_info() {
let mut v = [Severity::Info, Severity::Fix, Severity::Warn];
v.sort();
assert_eq!(v, [Severity::Fix, Severity::Warn, Severity::Info]);
}
#[test]
fn every_fixkind_variant_serializes() {
let variants = [
FixKind::CargoConfig {
key_path: "[target.aarch64-apple-darwin]".into(),
value: "linker = \"clang\"".into(),
},
FixKind::CargoToml {
section: "profile.dev".into(),
key: "debug".into(),
value: "1".into(),
},
FixKind::ShellCommand("cargo update".into()),
FixKind::Manual("do the thing".into()),
];
for kind in variants {
let json = serde_json::to_value(&kind).expect("FixKind must serialize");
assert!(json.is_object());
assert_eq!(json.as_object().unwrap().len(), 1);
}
}
#[test]
fn finding_serializes_with_expected_fields() {
let finding = Finding {
severity: Severity::Warn,
category: Category::Profile,
impact: Impact::Medium,
title: "t".into(),
description: "d".into(),
fix: None,
};
let json = serde_json::to_value(&finding).unwrap();
for key in ["severity", "category", "impact", "title", "description", "fix"] {
assert!(json.get(key).is_some(), "missing field {key}");
}
assert_eq!(json["severity"], "Warn");
}
}