Skip to main content

rscheck_cli/
report.rs

1use serde::{Deserialize, Serialize};
2
3use crate::config::Level;
4use crate::rules::{RuleBackend, RuleFamily};
5use crate::span::Span;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "lowercase")]
9pub enum Severity {
10    Info,
11    Warn,
12    Deny,
13}
14
15impl Severity {
16    #[must_use]
17    pub fn exit_code(self) -> i32 {
18        match self {
19            Self::Info => 0,
20            Self::Warn => 1,
21            Self::Deny => 2,
22        }
23    }
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "lowercase")]
28pub enum FixSafety {
29    Safe,
30    Unsafe,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "snake_case")]
35pub enum FindingLabelKind {
36    Primary,
37    Secondary,
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct FindingLabel {
42    pub kind: FindingLabelKind,
43    pub span: Span,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub message: Option<String>,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
49#[serde(rename_all = "snake_case")]
50pub enum FindingNoteKind {
51    Note,
52    Help,
53    Info,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct FindingNote {
58    pub kind: FindingNoteKind,
59    pub message: String,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct TextEdit {
64    pub file: String,
65    pub byte_start: u32,
66    pub byte_end: u32,
67    pub replacement: String,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Fix {
72    pub id: String,
73    pub safety: FixSafety,
74    pub message: String,
75    #[serde(default, skip_serializing_if = "Vec::is_empty")]
76    pub edits: Vec<TextEdit>,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct Finding {
81    pub rule_id: String,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub family: Option<RuleFamily>,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub engine: Option<RuleBackend>,
86    pub severity: Severity,
87    pub message: String,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub primary: Option<Span>,
90    #[serde(default, skip_serializing_if = "Vec::is_empty")]
91    pub secondary: Vec<Span>,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub help: Option<String>,
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub evidence: Option<String>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub confidence: Option<String>,
98    #[serde(default, skip_serializing_if = "Vec::is_empty")]
99    pub tags: Vec<String>,
100    #[serde(default, skip_serializing_if = "Vec::is_empty")]
101    pub labels: Vec<FindingLabel>,
102    #[serde(default, skip_serializing_if = "Vec::is_empty")]
103    pub notes: Vec<FindingNote>,
104    #[serde(default, skip_serializing_if = "Vec::is_empty")]
105    pub fixes: Vec<Fix>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct RuleCatalogEntry {
110    pub id: String,
111    pub family: RuleFamily,
112    pub backend: RuleBackend,
113    pub default_level: Level,
114    pub summary: String,
115    #[serde(default)]
116    pub fixable: bool,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct AdapterRun {
121    pub name: String,
122    pub enabled: bool,
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub toolchain: Option<String>,
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub status: Option<String>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct ToolchainSummary {
131    pub requested: String,
132    pub resolved: String,
133    pub semantic: String,
134    #[serde(default)]
135    pub nightly_available: bool,
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub reason: Option<String>,
138}
139
140#[derive(Debug, Clone, Default, Serialize, Deserialize)]
141pub struct RunSummary {
142    #[serde(default)]
143    pub engine_used: Vec<RuleBackend>,
144    #[serde(default)]
145    pub semantic_backend_available: bool,
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub semantic_backend_reason: Option<String>,
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub toolchain: Option<ToolchainSummary>,
150    #[serde(default, skip_serializing_if = "Vec::is_empty")]
151    pub skipped_rules: Vec<String>,
152    #[serde(default, skip_serializing_if = "Vec::is_empty")]
153    pub adapter_runs: Vec<AdapterRun>,
154}
155
156#[derive(Debug, Clone, Default, Serialize, Deserialize)]
157pub struct Metrics {
158    #[serde(default)]
159    pub per_file: Vec<FileMetrics>,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct FileMetrics {
164    pub path: String,
165    #[serde(default)]
166    pub cyclomatic_sum: u32,
167    #[serde(default)]
168    pub cyclomatic_max_fn: u32,
169}
170
171#[derive(Debug, Clone, Default, Serialize, Deserialize)]
172pub struct Report {
173    #[serde(default)]
174    pub findings: Vec<Finding>,
175    #[serde(default)]
176    pub metrics: Metrics,
177    #[serde(default, skip_serializing_if = "Vec::is_empty")]
178    pub rule_catalog: Vec<RuleCatalogEntry>,
179    #[serde(default)]
180    pub summary: RunSummary,
181}
182
183impl Report {
184    pub fn worst_severity(&self) -> Severity {
185        self.findings
186            .iter()
187            .map(|f| f.severity)
188            .max_by_key(|s| s.exit_code())
189            .unwrap_or(Severity::Info)
190    }
191}