circomspect_program_structure/program_library/
report.rs

1use anyhow::anyhow;
2use std::cmp::Ordering;
3use std::fmt::Display;
4use std::str::FromStr;
5
6use codespan_reporting::diagnostic::{Diagnostic, Label};
7
8use super::report_code::ReportCode;
9use super::file_definition::{FileID, FileLocation};
10
11pub type ReportCollection = Vec<Report>;
12pub type DiagnosticCode = String;
13pub type ReportLabel = Label<FileID>;
14type ReportNote = String;
15
16#[derive(Copy, Clone, Debug, PartialEq, Eq)]
17pub enum MessageCategory {
18    Error,
19    Warning,
20    Info,
21}
22
23/// Message categories are linearly ordered.
24impl PartialOrd for MessageCategory {
25    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
26        Some(self.cmp(other))
27    }
28}
29
30impl Ord for MessageCategory {
31    fn cmp(&self, other: &Self) -> Ordering {
32        use MessageCategory::*;
33        match (self, other) {
34            // `Info <= _`
35            (Info, Info) => Ordering::Equal,
36            (Info, Warning) | (Info, Error) => Ordering::Less,
37            // `Warning <= _`
38            (Warning, Warning) => Ordering::Equal,
39            (Warning, Error) => Ordering::Less,
40            // `Error <= _`
41            (Error, Error) => Ordering::Equal,
42            // All other cases are on the form `_ >= _`.
43            _ => Ordering::Greater,
44        }
45    }
46}
47
48impl Display for MessageCategory {
49    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50        use MessageCategory::*;
51        match self {
52            Error => write!(f, "error"),
53            Warning => write!(f, "warning"),
54            Info => write!(f, "info"),
55        }
56    }
57}
58
59impl FromStr for MessageCategory {
60    type Err = anyhow::Error;
61
62    fn from_str(category: &str) -> Result<MessageCategory, Self::Err> {
63        match category.to_lowercase().as_str() {
64            "warning" => Ok(MessageCategory::Warning),
65            "info" => Ok(MessageCategory::Info),
66            "error" => Ok(MessageCategory::Error),
67            _ => Err(anyhow!("unknown level '{category}'")),
68        }
69    }
70}
71
72impl MessageCategory {
73    /// Convert message category to Sarif level.
74    pub fn to_level(&self) -> String {
75        use MessageCategory::*;
76        match self {
77            Error => "error",
78            Warning => "warning",
79            Info => "note",
80        }
81        .to_string()
82    }
83}
84
85#[derive(Clone)]
86pub struct Report {
87    category: MessageCategory,
88    message: String,
89    primary_file_ids: Vec<FileID>,
90    primary: Vec<ReportLabel>,
91    secondary: Vec<ReportLabel>,
92    notes: Vec<ReportNote>,
93    code: ReportCode,
94}
95
96impl Report {
97    fn new(category: MessageCategory, message: String, code: ReportCode) -> Report {
98        Report {
99            category,
100            message,
101            primary_file_ids: Vec::new(),
102            primary: Vec::new(),
103            secondary: Vec::new(),
104            notes: Vec::new(),
105            code,
106        }
107    }
108
109    pub fn error(message: String, code: ReportCode) -> Report {
110        Report::new(MessageCategory::Error, message, code)
111    }
112
113    pub fn warning(message: String, code: ReportCode) -> Report {
114        Report::new(MessageCategory::Warning, message, code)
115    }
116
117    pub fn info(message: String, code: ReportCode) -> Report {
118        Report::new(MessageCategory::Info, message, code)
119    }
120
121    pub fn add_primary(
122        &mut self,
123        location: FileLocation,
124        file_id: FileID,
125        message: String,
126    ) -> &mut Self {
127        let label = ReportLabel::primary(file_id, location).with_message(message);
128        self.primary_mut().push(label);
129        self.primary_file_ids_mut().push(file_id);
130        self
131    }
132
133    pub fn add_secondary(
134        &mut self,
135        location: FileLocation,
136        file_id: FileID,
137        possible_message: Option<String>,
138    ) -> &mut Self {
139        let mut label = ReportLabel::secondary(file_id, location);
140        if let Some(message) = possible_message {
141            label = label.with_message(message);
142        }
143        self.secondary_mut().push(label);
144        self
145    }
146
147    pub fn add_note(&mut self, note: String) -> &mut Self {
148        self.notes_mut().push(note);
149        self
150    }
151
152    pub fn to_diagnostic(&self, verbose: bool) -> Diagnostic<FileID> {
153        let mut labels = self.primary().clone();
154        let mut secondary = self.secondary().clone();
155        labels.append(&mut secondary);
156
157        let diagnostic = match self.category() {
158            MessageCategory::Error => Diagnostic::error(),
159            MessageCategory::Warning => Diagnostic::warning(),
160            MessageCategory::Info => Diagnostic::note(),
161        }
162        .with_message(self.message())
163        .with_labels(labels);
164
165        let mut notes = self.notes().clone();
166        if let Some(url) = self.code().url() {
167            // Add URL to documentation if available.
168            notes.push(format!("For more details, see {url}."));
169        }
170        if verbose {
171            // Add report code and note on `--allow ID`.
172            notes.push(format!("To ignore this type of result, use `--allow {}`.", self.id()));
173            diagnostic.with_code(self.id()).with_notes(notes)
174        } else {
175            diagnostic.with_notes(notes)
176        }
177    }
178
179    pub fn primary_file_ids(&self) -> &Vec<FileID> {
180        &self.primary_file_ids
181    }
182
183    fn primary_file_ids_mut(&mut self) -> &mut Vec<FileID> {
184        &mut self.primary_file_ids
185    }
186
187    pub fn category(&self) -> &MessageCategory {
188        &self.category
189    }
190
191    pub fn message(&self) -> &String {
192        &self.message
193    }
194
195    pub fn primary(&self) -> &Vec<ReportLabel> {
196        &self.primary
197    }
198
199    fn primary_mut(&mut self) -> &mut Vec<ReportLabel> {
200        &mut self.primary
201    }
202
203    pub fn secondary(&self) -> &Vec<ReportLabel> {
204        &self.secondary
205    }
206
207    fn secondary_mut(&mut self) -> &mut Vec<ReportLabel> {
208        &mut self.secondary
209    }
210
211    pub fn notes(&self) -> &Vec<ReportNote> {
212        &self.notes
213    }
214
215    fn notes_mut(&mut self) -> &mut Vec<ReportNote> {
216        &mut self.notes
217    }
218
219    pub fn code(&self) -> &ReportCode {
220        &self.code
221    }
222
223    pub fn id(&self) -> String {
224        self.code.id()
225    }
226
227    pub fn name(&self) -> String {
228        self.code.name()
229    }
230}