hax_types/diagnostics/
message.rs

1use crate::cli_options::{Backend, BackendName, MessageFormat};
2use crate::diagnostics::report::ReportCtx;
3use crate::prelude::*;
4
5#[derive_group(Serializers)]
6#[derive(Debug, Clone, JsonSchema, Hash, Eq, PartialEq)]
7#[repr(u8)]
8pub enum HaxMessage {
9    Diagnostic {
10        diagnostic: super::Diagnostics,
11        working_dir: Option<PathBuf>,
12    } = 254,
13    BinaryNotFound {
14        binary_name: String,
15        env_var: String,
16        hint: Option<String>,
17    } = 0,
18    ProducedFile {
19        path: PathBuf,
20        wrote: bool,
21    } = 1,
22    HaxEngineFailure {
23        exit_code: i32,
24    } = 2,
25    CargoBuildFailure = 3,
26    WarnExperimentalBackend {
27        backend: Backend,
28    } = 4,
29    ProfilingData(crate::engine_api::ProfilingData) = 5,
30    Stats {
31        errors_per_item: Vec<(hax_frontend_exporter::DefId, usize)>,
32    } = 6,
33    GenericError {
34        message: String,
35    } = 7,
36    GenericWarning {
37        message: String,
38    } = 8,
39    RunningStep {
40        step: String,
41    } = 9,
42    SubprocessOutput {
43        prefix: String,
44        line: String,
45    } = 10,
46    OutputTruncated {
47        prefix: String,
48        remaining: usize,
49        log_path: PathBuf,
50    } = 11,
51    UnsupportedOption {
52        option: String,
53        backend: BackendName,
54    } = 12,
55}
56
57impl HaxMessage {
58    // https://doc.rust-lang.org/reference/items/enumerations.html#pointer-casting
59    pub fn discriminant(&self) -> u16 {
60        unsafe { *(self as *const Self as *const u16) }
61    }
62
63    pub fn code(&self) -> String {
64        match self {
65            HaxMessage::Diagnostic { diagnostic, .. } => diagnostic.kind.code(),
66            _ => format!("CARGOHAX{:0>4}", self.discriminant()),
67        }
68    }
69}
70
71const ENGINE_BINARY_NAME: &str = "hax-engine";
72
73use annotate_snippets::{Level, Renderer};
74
75impl HaxMessage {
76    pub fn report(self, message_format: MessageFormat, rctx: Option<&mut ReportCtx>) {
77        if let Some(rendered) = self.render(message_format, rctx) {
78            println!("{rendered}")
79        }
80    }
81    pub fn report_styled(self, rctx: Option<&mut ReportCtx>) {
82        println!("{}", self.render_styled(rctx))
83    }
84
85    pub fn render(
86        self,
87        message_format: MessageFormat,
88        mut rctx: Option<&mut ReportCtx>,
89    ) -> Option<String> {
90        if let (Some(r), HaxMessage::Diagnostic { diagnostic, .. }) = (rctx.as_mut(), &self)
91            && r.seen_already(diagnostic.clone())
92        {
93            return None;
94        }
95        Some(match message_format {
96            MessageFormat::Json => serde_json::to_string(&self).unwrap(),
97            MessageFormat::Human => self.render_styled(rctx),
98        })
99    }
100    pub fn render_styled(self, rctx: Option<&mut ReportCtx>) -> String {
101        let renderer = Renderer::styled();
102        match self {
103            Self::Diagnostic {
104                diagnostic,
105                working_dir,
106            } => {
107                let mut _rctx = None;
108                let rctx = rctx.unwrap_or_else(|| _rctx.get_or_insert(ReportCtx::default()));
109                diagnostic.with_message(
110                    rctx,
111                    working_dir.as_ref().map(PathBuf::as_path),
112                    Level::Error,
113                    |msg| format!("{}", renderer.render(msg)),
114                )
115            }
116            Self::BinaryNotFound {
117                binary_name,
118                env_var,
119                hint,
120            } => {
121                use colored::Colorize;
122                let mut message = format!(
123                    "hax: The binary [{}] was not found in your [PATH].\n\
124                     Please make sure it is installed and is in PATH!\n\
125                     Hint: set the [{}] environment variable to provide its path explicitly.",
126                    binary_name, env_var
127                );
128                if let Some(hint) = hint {
129                    message.push_str(&format!("\n{}", hint.bright_black()));
130                }
131                format!("{}", renderer.render(Level::Error.title(&message)))
132            }
133            Self::ProducedFile { mut path, wrote } => {
134                // Make path relative if possible
135                if let Ok(current_dir) = std::env::current_dir() {
136                    if let Ok(relative) = path.strip_prefix(current_dir) {
137                        path = PathBuf::from(".").join(relative).to_path_buf();
138                    }
139                }
140                let title = if wrote {
141                    format!("hax: wrote file {}", path.display())
142                } else {
143                    format!("hax: unchanged file {}", path.display())
144                };
145                format!("{}", renderer.render(Level::Info.title(&title)))
146            }
147            Self::HaxEngineFailure { exit_code } => {
148                let title = format!(
149                    "hax: {} exited with non-zero code {}",
150                    ENGINE_BINARY_NAME, exit_code,
151                );
152                format!("{}", renderer.render(Level::Error.title(&title)))
153            }
154            Self::ProfilingData(data) => {
155                fn format_with_dot(shift: u32, n: u64) -> String {
156                    let factor = 10u64.pow(shift);
157                    format!("{}.{}", n / factor, n % factor)
158                }
159                let title = format!(
160                    "hax[profiling]: {}: {}ms, memory={}, {} item{}{}",
161                    data.context,
162                    format_with_dot(6, data.time_ns),
163                    data.memory,
164                    data.quantity,
165                    if data.quantity > 1 { "s" } else { "" },
166                    if data.errored {
167                        " (note: this failed!)"
168                    } else {
169                        ""
170                    }
171                );
172                format!("{}", renderer.render(Level::Info.title(&title)))
173            }
174            Self::Stats { errors_per_item } => {
175                let success_items = errors_per_item.iter().filter(|(_, n)| *n == 0).count();
176                let total = errors_per_item.len();
177                let title = format!(
178                    "hax: {}/{} items were successfully translated ({}% success rate)",
179                    success_items,
180                    total,
181                    (success_items * 100) / total
182                );
183                format!("{}", renderer.render(Level::Info.title(&title)))
184            }
185            Self::CargoBuildFailure => {
186                let title =
187                    "hax: running `cargo build` was not successful, continuing anyway.".to_string();
188                format!("{}", renderer.render(Level::Warning.title(&title)))
189            }
190            Self::WarnExperimentalBackend { backend } => {
191                let title = format!(
192                    "hax: Experimental backend \"{}\" is work in progress.",
193                    backend
194                );
195                format!("{}", renderer.render(Level::Warning.title(&title)))
196            }
197            Self::GenericError { message } => {
198                let title = format!("hax: {}", message);
199                format!("{}", renderer.render(Level::Error.title(&title)))
200            }
201            Self::GenericWarning { message } => {
202                let title = format!("hax: {}", message);
203                format!("{}", renderer.render(Level::Warning.title(&title)))
204            }
205            Self::RunningStep { step } => {
206                use colored::Colorize;
207                format!("{:>12} {}", "Running".bold().green(), step)
208            }
209            Self::SubprocessOutput { prefix, line } => {
210                format!("{:>12} > {}", prefix, line)
211            }
212            Self::OutputTruncated {
213                prefix,
214                remaining,
215                log_path,
216            } => {
217                format!(
218                    "{:>12} > ... ({} more lines, full output in {})",
219                    prefix,
220                    remaining,
221                    log_path.display()
222                )
223            }
224            Self::UnsupportedOption { option, backend } => {
225                let title = format!(
226                    "hax: option {} is not supported by the {} backend and will be ignored",
227                    option, backend
228                );
229                format!("{}", renderer.render(Level::Warning.title(&title)))
230            }
231        }
232    }
233}