1use crate::bt::Path;
2use std::fmt;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
5pub enum Severity {
6 Fatal,
7 Warning,
8 Info,
9}
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum Source {
13 Both,
14 StandardOnly,
15 ArtefactOnly,
16 Crate,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Finding {
21 pub id: &'static str,
22 pub severity: Severity,
23 pub path: Path,
24 pub message: String,
25 pub detail: Option<String>,
26 pub hint: Option<String>,
27}
28
29impl Finding {
30 pub fn fatal(id: &'static str, path: Path, message: impl Into<String>) -> Self {
31 Self {
32 id,
33 severity: Severity::Fatal,
34 path,
35 message: message.into(),
36 detail: None,
37 hint: None,
38 }
39 }
40
41 pub fn warning(id: &'static str, path: Path, message: impl Into<String>) -> Self {
42 Self {
43 id,
44 severity: Severity::Warning,
45 path,
46 message: message.into(),
47 detail: None,
48 hint: None,
49 }
50 }
51}
52
53#[derive(Debug, Clone, Default, PartialEq, Eq)]
54pub struct Report {
55 pub findings: Vec<Finding>,
56 pub profile_slug: &'static str,
57 pub rules_checked: usize,
58}
59
60impl Report {
61 pub fn ok(&self) -> bool {
62 !self.findings.iter().any(|f| f.severity == Severity::Fatal)
63 }
64
65 pub fn push(&mut self, finding: Finding) {
66 self.findings.push(finding);
67 }
68
69 pub fn sort_stable(&mut self) {
70 self.findings.sort_by(|a, b| {
71 a.severity
72 .cmp(&b.severity)
73 .then_with(|| a.path.to_string().cmp(&b.path.to_string()))
74 .then_with(|| a.id.cmp(b.id))
75 });
76 }
77}
78
79impl fmt::Display for Finding {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 write!(f, "[{}] {} — {}", self.id, self.path, self.message)?;
82 if let Some(d) = &self.detail {
83 write!(f, " ({d})")?;
84 }
85 Ok(())
86 }
87}
88
89impl fmt::Display for Report {
90 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91 if self.ok() {
92 return write!(f, "valid");
93 }
94 for finding in &self.findings {
95 writeln!(f, "{finding}")?;
96 }
97 Ok(())
98 }
99}