use crate::error::{self, Context};
use crate::fmt::{self, Write};
use crate::input::{Bytes, Input, Private};
use super::{DisplayBase, InputDisplay, PreferredFormat};
const DEFAULT_MAX_WIDTH: usize = 80;
const INVALID_SPAN_ERROR: &str = "\
note: error span is not within the error input indicating the
concrete error being used has a bug. Consider raising an
issue with the maintainer!
";
#[derive(Clone)]
#[must_use = "error displays must be written"]
pub struct ErrorDisplay<'a, T> {
error: &'a T,
banner: bool,
format: PreferredFormat,
input_max_width: usize,
}
impl<'a, 'i, T> ErrorDisplay<'a, T>
where
T: error::Details<'i>,
{
pub fn new(error: &'a T) -> Self {
let format = if error.input().is_string() {
PreferredFormat::Str
} else {
PreferredFormat::Bytes
};
Self {
error,
format,
banner: false,
input_max_width: DEFAULT_MAX_WIDTH,
}
}
pub fn from_formatter(error: &'a T, f: &fmt::Formatter<'_>) -> Self {
if f.alternate() {
Self::new(error).str_hint()
} else {
Self::new(error)
}
}
pub fn banner(mut self, value: bool) -> Self {
self.banner = value;
self
}
pub fn input_max_width(mut self, value: usize) -> Self {
self.input_max_width = value;
self
}
pub fn str_hint(self) -> Self {
match self.format {
PreferredFormat::Bytes | PreferredFormat::BytesAscii => {
self.format(PreferredFormat::Str)
}
_ => self,
}
}
pub fn format(mut self, format: PreferredFormat) -> Self {
self.format = format;
self
}
fn write_sections(&self, w: &mut dyn Write) -> fmt::Result {
let input = self.error.input();
let root = self.error.backtrace().root();
w.write_str("error attempting to ")?;
root.operation().description(w)?;
w.write_str(": ")?;
self.error.description(w)?;
w.write_char('\n')?;
let input_display = self.configure_input_display(input.display());
let input = input.into_bytes();
if let Some(expected_value) = self.error.expected() {
let expected_display = self.configure_input_display(expected_value.display());
w.write_str("expected:\n")?;
write_input(w, expected_display, false)?;
w.write_str("in:\n")?;
}
if root.span.is_within(input.span()) {
write_input(w, input_display.span(root.span, self.input_max_width), true)?;
} else {
w.write_str(INVALID_SPAN_ERROR)?;
w.write_str("input:\n")?;
write_input(w, input_display, false)?;
}
w.write_str("additional:\n ")?;
if let Some(span_range) = root.span.range_of(input.span()) {
match self.format {
PreferredFormat::Str | PreferredFormat::StrCjk | PreferredFormat::BytesAscii => {
w.write_str("error line: ")?;
w.write_usize(line_offset(&input, span_range.start))?;
w.write_str(", ")?;
}
_ => (),
}
w.write_str("error offset: ")?;
w.write_usize(span_range.start)?;
w.write_str(", input length: ")?;
w.write_usize(input.len())?;
} else {
w.write_str("error: ")?;
DisplayBase::fmt(&root.span, w)?;
w.write_str("input: ")?;
DisplayBase::fmt(&input.span(), w)?;
}
w.write_char('\n')?;
w.write_str("backtrace:")?;
let mut child_index = 1;
let mut last_parent_depth = 0;
let write_success = self.error.backtrace().walk(&mut |parent_depth, context| {
let mut write = || {
w.write_str("\n ")?;
if parent_depth == last_parent_depth {
w.write_str(" ")?;
w.write_usize(child_index)?;
child_index += 1;
} else {
child_index = 1;
last_parent_depth = parent_depth;
w.write_usize(parent_depth)?;
}
w.write_str(". `")?;
context.operation().description(w)?;
w.write_char('`')?;
if context.has_expected() {
w.write_str(" (expected ")?;
context.expected(w)?;
w.write_char(')')?;
}
fmt::Result::Ok(())
};
write().is_ok()
});
if write_success {
Ok(())
} else {
Err(fmt::Error)
}
}
fn configure_input_display<'b>(&self, display: InputDisplay<'b>) -> InputDisplay<'b> {
display.format(self.format)
}
}
impl<'a, 'i, T> fmt::DisplayBase for ErrorDisplay<'a, T>
where
T: error::Details<'i>,
{
fn fmt(&self, w: &mut dyn Write) -> fmt::Result {
if self.banner {
w.write_str("\n-- INPUT ERROR ---------------------------------------------\n")?;
self.write_sections(w)?;
w.write_str("\n------------------------------------------------------------\n")
} else {
self.write_sections(w)
}
}
}
impl<'a, 'i, T> fmt::Debug for ErrorDisplay<'a, T>
where
T: error::Details<'i>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::DisplayBase::fmt(self, f)
}
}
impl<'a, 'i, T> fmt::Display for ErrorDisplay<'a, T>
where
T: error::Details<'i>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::DisplayBase::fmt(self, f)
}
}
fn line_offset(input: &Bytes<'_>, span_offset: usize) -> usize {
match input.clone().split_at_opt(span_offset) {
Some((before_span, _)) => before_span.count(b'\n') + 1,
None => 0,
}
}
fn write_input(w: &mut dyn Write, input: InputDisplay<'_>, underline: bool) -> fmt::Result {
let input = input.prepare();
w.write_str("> ")?;
fmt::DisplayBase::fmt(&input, w)?;
w.write_char('\n')?;
if underline {
w.write_str(" ")?;
fmt::DisplayBase::fmt(&input.underline(), w)?;
w.write_char('\n')?;
}
Ok(())
}