circomspect_program_structure/program_library/
report.rs1use 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
23impl 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, Info) => Ordering::Equal,
36 (Info, Warning) | (Info, Error) => Ordering::Less,
37 (Warning, Warning) => Ordering::Equal,
39 (Warning, Error) => Ordering::Less,
40 (Error, Error) => Ordering::Equal,
42 _ => 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 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 notes.push(format!("For more details, see {url}."));
169 }
170 if verbose {
171 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}