derail-report 0.1.0

Tools for reporting `derail::Error`s.
Documentation
//! Oneline report implementation.

use core::{
    fmt::{self, Write as _},
    marker::PhantomData,
    ops::ControlFlow,
};

use derail::{Error, ErrorExt as _, VisitContext, Visitor, VisitorExt as _};

use crate::{BufferFactory, buffer::Buffer, sealed::Sealed};

/// An element in the buffer.
#[derive(Clone, Copy, Default)]
struct Element {
    /// Whether this node has another sibling.
    has_next_sibling: bool,

    /// Whether this node has any children.
    has_children: bool,
}

/// [`Display`]s [`Error`]s on one line. Suitable for tracing.
///
/// If the length of any chains of nodes from a root to a leaf is greater than
/// [`StackFactory`]'s `LEN` plus one, the output will not be as syntactically
/// consistent, but information will not be lost.
///
/// [`Display`]: fmt::Display
/// [`StackFactory`]: crate::StackFactory
#[cfg_attr(
    feature = "alloc",
    doc = "\n\n[`HeapFactory`](crate::HeapFactory) does not have this \
           limitation."
)]
pub fn display<'a, I, E, B>(errors: I) -> impl fmt::Display
where
    I: IntoIterator<Item = &'a E> + Clone,
    E: Error + ?Sized + 'a,
    B: BufferFactory,
{
    DisplayImpl(errors, PhantomData::<B>)
}

/// [`Display`](fmt::Display) implementation.
struct DisplayImpl<I, B>(I, PhantomData<B>);

impl<'a, I, E, B> fmt::Display for DisplayImpl<I, B>
where
    I: IntoIterator<Item = &'a E> + Clone,
    E: Error + ?Sized + 'a,
    B: BufferFactory,
{
    fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[")?;
        let mut visitor = VisitorImpl {
            writer: &mut f,
            result: Ok(()),
            buffer: B::new(Sealed),
            details: PhantomData::<E::Details>,
        };
        let _: ControlFlow<(), ()> = visitor.visit_many(self.0.clone());
        visitor.result?;
        write!(f, "]")
    }
}

/// Write the contents of a string, escaping various characters.
struct WriteEscaped<W>(W);

impl<W> fmt::Write for WriteEscaped<W>
where
    W: fmt::Write,
{
    fn write_str(&mut self, s: &str) -> fmt::Result {
        write!(self.0, "{}", s.escape_debug())
    }
}

/// [`Visitor`] implementation.
struct VisitorImpl<W, B, D> {
    /// The writer to write to.
    writer: W,

    /// The write result.
    result: fmt::Result,

    /// The buffer.
    buffer: B,

    /// The [`Error::Details`] type.
    details: PhantomData<D>,
}

impl<W, B, D> Visitor for VisitorImpl<W, B, D>
where
    W: fmt::Write,
    B: Buffer<Element>,
{
    type Details = D;

    fn visit(
        &mut self,
        visitee: &dyn Error<Details = Self::Details>,
        ctx: VisitContext<'_, Self::Details>,
    ) -> ControlFlow<()> {
        attempt!(write!(self.writer, "\""), self.result);
        attempt!(
            write!(WriteEscaped(&mut self.writer), "{visitee}"),
            self.result
        );
        attempt!(write!(self.writer, "\""), self.result);

        let has_next_sibling = ctx.next_sibling().is_some();
        let has_children = visitee.has_children();

        if let Some(x) = self.buffer.last_mut(Sealed) {
            x.has_next_sibling = has_next_sibling;
            x.has_children = has_children;
        }

        if has_next_sibling && !has_children {
            attempt!(write!(self.writer, ","), self.result);
        }

        ControlFlow::Continue(())
    }

    fn push(&mut self) -> ControlFlow<()> {
        self.buffer.push(Sealed);

        attempt!(write!(self.writer, ":["), self.result);

        ControlFlow::Continue(())
    }

    fn pop(&mut self) -> ControlFlow<()> {
        self.buffer.pop(Sealed);

        attempt!(write!(self.writer, "]"), self.result);

        if let Some(x) = self.buffer.last_mut(Sealed) {
            if x.has_next_sibling && x.has_children {
                attempt!(write!(self.writer, ","), self.result);
                x.has_next_sibling = false;
                x.has_children = false;
            }
        }

        ControlFlow::Continue(())
    }
}