dangerous/display/
error.rs

1use crate::error::{self, Context};
2use crate::fmt::{self, Write};
3use crate::input::{Bytes, Input, Private};
4
5use super::{DisplayBase, InputDisplay, PreferredFormat};
6
7const DEFAULT_MAX_WIDTH: usize = 80;
8const INVALID_SPAN_ERROR: &str = "\
9note: error span is not within the error input indicating the
10      concrete error being used has a bug. Consider raising an
11      issue with the maintainer!
12";
13
14/// Provides configurable [`error::Details`] formatting.
15#[derive(Clone)]
16#[must_use = "error displays must be written"]
17pub struct ErrorDisplay<'a, T> {
18    error: &'a T,
19    banner: bool,
20    format: PreferredFormat,
21    input_max_width: usize,
22}
23
24impl<'a, 'i, T> ErrorDisplay<'a, T>
25where
26    T: error::Details<'i>,
27{
28    /// Create a new `ErrorDisplay` given [`error::Details`].
29    pub fn new(error: &'a T) -> Self {
30        let format = if error.input().is_string() {
31            PreferredFormat::Str
32        } else {
33            PreferredFormat::Bytes
34        };
35        Self {
36            error,
37            format,
38            banner: false,
39            input_max_width: DEFAULT_MAX_WIDTH,
40        }
41    }
42
43    /// Derive an `ErrorDisplay` from a [`fmt::Formatter`] with defaults.
44    pub fn from_formatter(error: &'a T, f: &fmt::Formatter<'_>) -> Self {
45        if f.alternate() {
46            Self::new(error).str_hint()
47        } else {
48            Self::new(error)
49        }
50    }
51
52    /// Set whether or not a banner should printed around the error.
53    pub fn banner(mut self, value: bool) -> Self {
54        self.banner = value;
55        self
56    }
57
58    /// Set the `max-width` for wrapping error output.
59    pub fn input_max_width(mut self, value: usize) -> Self {
60        self.input_max_width = value;
61        self
62    }
63
64    /// Hint to the formatter that the [`crate::Input`] is a UTF-8 `str`.
65    pub fn str_hint(self) -> Self {
66        match self.format {
67            PreferredFormat::Bytes | PreferredFormat::BytesAscii => {
68                self.format(PreferredFormat::Str)
69            }
70            _ => self,
71        }
72    }
73
74    /// Set the preferred way to format the [`Input`].
75    pub fn format(mut self, format: PreferredFormat) -> Self {
76        self.format = format;
77        self
78    }
79
80    fn write_sections(&self, w: &mut dyn Write) -> fmt::Result {
81        let input = self.error.input();
82        let root = self.error.backtrace().root();
83        // Write description
84        w.write_str("error attempting to ")?;
85        root.operation().description(w)?;
86        w.write_str(": ")?;
87        self.error.description(w)?;
88        w.write_char('\n')?;
89        // Write inputs
90        let input_display = self.configure_input_display(input.display());
91        let input = input.into_bytes();
92        if let Some(expected_value) = self.error.expected() {
93            let expected_display = self.configure_input_display(expected_value.display());
94            w.write_str("expected:\n")?;
95            write_input(w, expected_display, false)?;
96            w.write_str("in:\n")?;
97        }
98        if root.span.is_within(input.span()) {
99            write_input(w, input_display.span(root.span, self.input_max_width), true)?;
100        } else {
101            w.write_str(INVALID_SPAN_ERROR)?;
102            w.write_str("input:\n")?;
103            write_input(w, input_display, false)?;
104        }
105        // Write additional
106        w.write_str("additional:\n  ")?;
107        if let Some(span_range) = root.span.range_of(input.span()) {
108            match self.format {
109                PreferredFormat::Str | PreferredFormat::StrCjk | PreferredFormat::BytesAscii => {
110                    w.write_str("error line: ")?;
111                    w.write_usize(line_offset(&input, span_range.start))?;
112                    w.write_str(", ")?;
113                }
114                _ => (),
115            }
116            w.write_str("error offset: ")?;
117            w.write_usize(span_range.start)?;
118            w.write_str(", input length: ")?;
119            w.write_usize(input.len())?;
120        } else {
121            w.write_str("error: ")?;
122            DisplayBase::fmt(&root.span, w)?;
123            w.write_str("input: ")?;
124            DisplayBase::fmt(&input.span(), w)?;
125        }
126        w.write_char('\n')?;
127        // Write context backtrace
128        w.write_str("backtrace:")?;
129        let mut child_index = 1;
130        let mut last_parent_depth = 0;
131        let write_success = self.error.backtrace().walk(&mut |parent_depth, context| {
132            let mut write = || {
133                w.write_str("\n  ")?;
134                if parent_depth == last_parent_depth {
135                    w.write_str("  ")?;
136                    w.write_usize(child_index)?;
137                    child_index += 1;
138                } else {
139                    child_index = 1;
140                    last_parent_depth = parent_depth;
141                    w.write_usize(parent_depth)?;
142                }
143                w.write_str(". `")?;
144                context.operation().description(w)?;
145                w.write_char('`')?;
146                if context.has_expected() {
147                    w.write_str(" (expected ")?;
148                    context.expected(w)?;
149                    w.write_char(')')?;
150                }
151                fmt::Result::Ok(())
152            };
153            write().is_ok()
154        });
155        if write_success {
156            Ok(())
157        } else {
158            Err(fmt::Error)
159        }
160    }
161
162    fn configure_input_display<'b>(&self, display: InputDisplay<'b>) -> InputDisplay<'b> {
163        display.format(self.format)
164    }
165}
166
167impl<'a, 'i, T> fmt::DisplayBase for ErrorDisplay<'a, T>
168where
169    T: error::Details<'i>,
170{
171    fn fmt(&self, w: &mut dyn Write) -> fmt::Result {
172        if self.banner {
173            w.write_str("\n-- INPUT ERROR ---------------------------------------------\n")?;
174            self.write_sections(w)?;
175            w.write_str("\n------------------------------------------------------------\n")
176        } else {
177            self.write_sections(w)
178        }
179    }
180}
181
182impl<'a, 'i, T> fmt::Debug for ErrorDisplay<'a, T>
183where
184    T: error::Details<'i>,
185{
186    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187        fmt::DisplayBase::fmt(self, f)
188    }
189}
190
191impl<'a, 'i, T> fmt::Display for ErrorDisplay<'a, T>
192where
193    T: error::Details<'i>,
194{
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        fmt::DisplayBase::fmt(self, f)
197    }
198}
199
200fn line_offset(input: &Bytes<'_>, span_offset: usize) -> usize {
201    match input.clone().split_at_opt(span_offset) {
202        Some((before_span, _)) => before_span.count(b'\n') + 1,
203        // Will never be reached in practical usage but we handle to avoid
204        // unwrapping.
205        None => 0,
206    }
207}
208
209fn write_input(w: &mut dyn Write, input: InputDisplay<'_>, underline: bool) -> fmt::Result {
210    let input = input.prepare();
211    w.write_str("> ")?;
212    fmt::DisplayBase::fmt(&input, w)?;
213    w.write_char('\n')?;
214    if underline {
215        w.write_str("  ")?;
216        fmt::DisplayBase::fmt(&input.underline(), w)?;
217        w.write_char('\n')?;
218    }
219    Ok(())
220}