use core::{error::Error, fmt, marker::PhantomData};
use derive_where::derive_where;
use crate::{
Format, chain,
connectors::{Connectors, Unicode},
indent::{Repeat, indented},
};
#[derive_where(Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Chain<C = Unicode>(PhantomData<fn() -> C>);
impl<E: Error + ?Sized, C: Connectors> Format<E> for Chain<C> {
fn fmt(error: &E, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (depth, e) in chain(&error).enumerate() {
match depth {
0 => write!(f, "{e}")?,
n => {
write!(f, "\n{}{}", Repeat(C::GAP, n - 1), C::LAST)?;
indented(f, Repeat(C::GAP, n), e)?;
}
}
}
Ok(())
}
}
impl<C: fmt::Debug + Default> fmt::Debug for Chain<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Chain").field(&C::default()).finish()
}
}
#[cfg(test)]
mod tests {
use core::fmt;
use itertools::Itertools;
use crate::{
Chain, Format, FormatError, Formatted, chain,
connectors::{Ascii, Connectors, Unicode},
tests::{Error, Inner},
};
#[test]
fn test_chain_no_source() {
let error = Error::One;
assert_eq!(error.chain().to_string(), "One");
}
#[test]
fn test_chain_one_source() {
let error = Error::Two(Inner::A);
assert_eq!(error.chain().to_string(), "Two\n└─ InnerA");
}
#[test]
fn test_chain_nested() {
let error = Error::Two(Inner::B);
assert_eq!(error.chain().to_string(), "Two\n└─ InnerB");
}
#[test]
fn test_chain_ascii() {
let error = Error::Two(Inner::A);
assert_eq!(
Formatted::<_, Chain<Ascii>>::new(error).to_string(),
"Two\n`- InnerA"
);
}
#[test]
fn test_chain_custom_connectors() {
struct Arrow;
impl Connectors for Arrow {
const LAST: &'static str = "|-> ";
const GAP: &'static str = " ";
}
let error = Error::Two(Inner::A);
assert_eq!(
Formatted::<_, Chain<Arrow>>::new(error).to_string(),
"Two\n|-> InnerA"
);
}
#[test]
fn test_chain_debug_default_params() {
let c = Chain::<Unicode>::default();
assert_eq!(format!("{c:?}"), "Chain(Unicode)");
}
#[test]
fn test_chain_debug_custom_params() {
let c = Chain::<Ascii>::default();
assert_eq!(format!("{c:?}"), "Chain(Ascii)");
}
#[test]
fn test_custom_chain_via_format() {
struct AsciiChain;
impl<E: core::error::Error + ?Sized> Format<E> for AsciiChain {
fn fmt(error: &E, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let formatted = chain(&error)
.enumerate()
.format_with("\n", |(depth, e), write| match depth {
0 => write(&format_args!("{e}")),
n => write(&format_args!("{:width$}|-- {e}", "", width = (n - 1) * 2)),
});
write!(f, "{formatted}")
}
}
let error = Error::Two(Inner::A);
assert_eq!(
Formatted::<_, AsciiChain>::new(error).to_string(),
"Two\n|-- InnerA"
);
}
}