nu_protocol/errors/
report_error.rs1use std::hash::{DefaultHasher, Hash, Hasher};
5
6use crate::{
7 CompileError, ErrorStyle, ParseError, ParseWarning, ShellError, ShellWarning,
8 engine::{EngineState, StateWorkingSet},
9};
10use miette::{
11 LabeledSpan, MietteHandlerOpts, NarratableReportHandler, ReportHandler, RgbColors, Severity,
12 SourceCode,
13};
14use serde::{Deserialize, Serialize};
15use thiserror::Error;
16
17#[derive(Error)]
20#[error("{diagnostic}")]
21struct CliError<'src> {
22 diagnostic: &'src dyn miette::Diagnostic,
23 working_set: &'src StateWorkingSet<'src>,
24 default_code: Option<&'static str>,
26}
27
28impl<'src> CliError<'src> {
29 pub fn new(
30 diagnostic: &'src dyn miette::Diagnostic,
31 working_set: &'src StateWorkingSet<'src>,
32 default_code: Option<&'static str>,
33 ) -> Self {
34 CliError {
35 diagnostic,
36 working_set,
37 default_code,
38 }
39 }
40}
41
42#[derive(Default)]
46pub struct ReportLog(Vec<u64>);
47
48#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
50pub enum ReportMode {
51 FirstUse,
52 EveryUse,
53}
54
55pub trait Reportable {
57 fn report_mode(&self) -> ReportMode;
58}
59
60fn should_show_reportable<R>(engine_state: &EngineState, reportable: &R) -> bool
62where
63 R: Reportable + Hash,
64{
65 match reportable.report_mode() {
66 ReportMode::EveryUse => true,
67 ReportMode::FirstUse => {
68 let mut hasher = DefaultHasher::new();
69 reportable.hash(&mut hasher);
70 let hash = hasher.finish();
71
72 let mut report_log = engine_state
73 .report_log
74 .lock()
75 .expect("report log lock is poisioned");
76
77 match report_log.0.contains(&hash) {
78 true => false,
79 false => {
80 report_log.0.push(hash);
81 true
82 }
83 }
84 }
85 }
86}
87
88pub fn format_cli_error(
89 working_set: &StateWorkingSet,
90 error: &dyn miette::Diagnostic,
91 default_code: Option<&'static str>,
92) -> String {
93 format!(
94 "Error: {:?}",
95 CliError::new(error, working_set, default_code)
96 )
97}
98
99pub fn report_shell_error(engine_state: &EngineState, error: &ShellError) {
100 if engine_state.config.display_errors.should_show(error) {
101 let working_set = StateWorkingSet::new(engine_state);
102 report_error(&working_set, error, "nu::shell::error")
103 }
104}
105
106pub fn report_shell_warning(engine_state: &EngineState, warning: &ShellWarning) {
107 if should_show_reportable(engine_state, warning) {
108 report_warning(
109 &StateWorkingSet::new(engine_state),
110 warning,
111 "nu::shell::warning",
112 );
113 }
114}
115
116pub fn report_parse_error(working_set: &StateWorkingSet, error: &ParseError) {
117 report_error(working_set, error, "nu::parser::error");
118}
119
120pub fn report_parse_warning(working_set: &StateWorkingSet, warning: &ParseWarning) {
121 if should_show_reportable(working_set.permanent(), warning) {
122 report_warning(working_set, warning, "nu::parser::warning");
123 }
124}
125
126pub fn report_compile_error(working_set: &StateWorkingSet, error: &CompileError) {
127 report_error(working_set, error, "nu::compile::error");
128}
129
130pub fn report_experimental_option_warning(
131 working_set: &StateWorkingSet,
132 warning: &dyn miette::Diagnostic,
133) {
134 report_warning(working_set, warning, "nu::experimental_option::warning");
135}
136
137fn report_error(
138 working_set: &StateWorkingSet,
139 error: &dyn miette::Diagnostic,
140 default_code: &'static str,
141) {
142 eprintln!(
143 "Error: {:?}",
144 CliError::new(error, working_set, Some(default_code))
145 );
146 #[cfg(windows)]
148 {
149 let _ = nu_utils::enable_vt_processing();
150 }
151}
152
153fn report_warning(
154 working_set: &StateWorkingSet,
155 warning: &dyn miette::Diagnostic,
156 default_code: &'static str,
157) {
158 eprintln!(
159 "Warning: {:?}",
160 CliError::new(warning, working_set, Some(default_code))
161 );
162 #[cfg(windows)]
164 {
165 let _ = nu_utils::enable_vt_processing();
166 }
167}
168
169impl std::fmt::Debug for CliError<'_> {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 let config = self.working_set.get_config();
172
173 let ansi_support = config.use_ansi_coloring.get(self.working_set.permanent());
174
175 let error_style = &config.error_style;
176
177 let miette_handler: Box<dyn ReportHandler> = match error_style {
178 ErrorStyle::Plain => Box::new(NarratableReportHandler::new()),
179 ErrorStyle::Fancy => Box::new(
180 MietteHandlerOpts::new()
181 .rgb_colors(RgbColors::Never)
183 .color(ansi_support)
185 .unicode(ansi_support)
186 .terminal_links(ansi_support)
187 .build(),
188 ),
189 };
190
191 let _ = miette_handler.debug(self, f);
194
195 Ok(())
196 }
197}
198
199impl miette::Diagnostic for CliError<'_> {
200 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
201 self.diagnostic.code().or_else(|| {
202 self.default_code
203 .map(|code| Box::new(code) as Box<dyn std::fmt::Display>)
204 })
205 }
206
207 fn severity(&self) -> Option<Severity> {
208 self.diagnostic.severity()
209 }
210
211 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
212 self.diagnostic.help()
213 }
214
215 fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
216 self.diagnostic.url()
217 }
218
219 fn labels<'a>(&'a self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + 'a>> {
220 self.diagnostic.labels()
221 }
222
223 fn source_code(&self) -> Option<&dyn SourceCode> {
225 if let Some(source_code) = self.diagnostic.source_code() {
226 Some(source_code)
227 } else {
228 Some(&self.working_set)
229 }
230 }
231
232 fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
233 self.diagnostic.related()
234 }
235
236 fn diagnostic_source(&self) -> Option<&dyn miette::Diagnostic> {
237 self.diagnostic.diagnostic_source()
238 }
239}