use core::{
error::Error,
fmt::{self, Debug, Display, Formatter},
};
use derive_where::derive_where;
use alloc::{vec, vec::Vec};
use crate::{
AsDisplay, Format,
with_context::{Colon, WithContext},
};
mod iter;
mod node;
pub mod strategy;
pub use crate::connectors::{Ascii, Connectors, TreeConnectors, Unicode};
pub use iter::{IntoIter, Iter, IterMut};
pub use node::{Node, Subgroup};
pub use strategy::{Bullets, Joined, List, Tree};
#[derive_where(Clone, PartialEq, Eq, Hash; C, E, GC)]
#[derive_where(Default)]
pub enum ManyErrors<C, E, GC = C, F = Colon, GF = AsDisplay> {
#[derive_where(default)]
None,
One(Node<C, E, GC, F, GF>),
Many(Vec<Node<C, E, GC, F, GF>>),
}
impl<C: Debug, E: Debug, GC: Debug, F, GF> Debug for ManyErrors<C, E, GC, F, GF> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "None"),
Self::One(n) => f.debug_tuple("One").field(n).finish(),
Self::Many(v) => f.debug_list().entries(v.iter()).finish(),
}
}
}
impl<C, E, GC, F, GF> ManyErrors<C, E, GC, F, GF> {
pub const fn new() -> Self {
Self::None
}
pub const fn is_empty(&self) -> bool {
matches!(self, Self::None)
}
pub const fn len(&self) -> usize {
match self {
Self::None => 0,
Self::One(_) => 1,
Self::Many(v) => v.len(),
}
}
pub fn push(&mut self, context: C, error: E) {
self.push_node(Node::Leaf(WithContext::new(context, error)));
}
pub fn push_group(&mut self, context: GC, errors: Self) {
self.push_node(Node::Group(Subgroup::new(context, errors)));
}
pub fn push_node(&mut self, node: impl Into<Node<C, E, GC, F, GF>>) {
let prev = core::mem::take(self);
*self = match prev {
Self::None => Self::One(node.into()),
Self::One(first) => Self::Many(vec![first, node.into()]),
Self::Many(mut v) => {
v.push(node.into());
Self::Many(v)
}
};
}
pub fn into_result<T>(self, ok: T) -> Result<T, Self> {
match self {
Self::None => Ok(ok),
_ => Err(self),
}
}
pub fn with_formats<NewF, NewGF>(self) -> ManyErrors<C, E, GC, NewF, NewGF>
where
NewF: Format<WithContext<C, E, NewF>>,
NewGF: Format<GC>,
{
match self {
Self::None => ManyErrors::None,
Self::One(node) => ManyErrors::One(node.with_formats()),
Self::Many(nodes) => {
ManyErrors::Many(nodes.into_iter().map(Node::with_formats).collect())
}
}
}
}
impl<C, E, GC, F, GF> ManyErrors<C, E, GC, F, GF> {
pub fn tree(&self) -> crate::Formatted<&Self, Tree> {
crate::Formatted::new(self)
}
pub fn list(&self) -> crate::Formatted<&Self, List> {
crate::Formatted::new(self)
}
pub fn bullets(&self) -> crate::Formatted<&Self, Bullets> {
crate::Formatted::new(self)
}
pub fn joined(&self) -> crate::Formatted<&Self, Joined> {
crate::Formatted::new(self)
}
pub const fn formatted<F2>(&self) -> crate::Formatted<&Self, F2> {
crate::Formatted::new(self)
}
}
impl<C, E, GC, F, GF> Display for ManyErrors<C, E, GC, F, GF>
where
E: Error + 'static,
F: Format<WithContext<C, E, F>>,
GF: Format<GC>,
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
<strategy::Summary as Format<Self>>::fmt(self, f)
}
}
impl<C, E, GC, F, GF> Error for ManyErrors<C, E, GC, F, GF>
where
C: Debug,
GC: Debug,
E: Error + 'static,
F: Format<WithContext<C, E, F>>,
GF: Format<GC>,
{
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{Inner, Mid};
#[test]
fn test_new_is_none() {
let e = ManyErrors::<&str, Inner>::new();
assert!(matches!(e, ManyErrors::None));
assert!(e.is_empty());
assert_eq!(e.len(), 0);
}
#[test]
fn test_push_none_to_one() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("a", Inner::A);
assert!(matches!(e, ManyErrors::One(_)));
assert_eq!(e.len(), 1);
}
#[test]
fn test_push_one_to_many() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("a", Inner::A);
e.push("b", Inner::A);
assert!(matches!(e, ManyErrors::Many(_)));
assert_eq!(e.len(), 2);
}
#[test]
fn test_push_many_grows() {
let mut e = ManyErrors::<u32, Inner, &str>::new();
for i in 0..5u32 {
e.push(i, Inner::A);
}
assert_eq!(e.len(), 5);
}
#[test]
fn test_push_group() {
let mut inner = ManyErrors::<&str, Inner>::new();
inner.push("x", Inner::A);
inner.push("y", Inner::B);
let mut outer = ManyErrors::<&str, Inner>::new();
outer.push_group("region", inner);
assert_eq!(outer.len(), 1);
assert!(matches!(outer, ManyErrors::One(Node::Group(_))));
}
#[test]
fn test_push_leaf_and_group() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("leaf", Inner::A);
let mut sub = ManyErrors::new();
sub.push("sub-leaf", Inner::B);
e.push_group("group", sub);
assert_eq!(e.len(), 2);
}
#[test]
fn test_into_result_none_ok() {
let e = ManyErrors::<&str, Inner>::new();
assert_eq!(e.into_result(42), Ok(42));
}
#[test]
fn test_into_result_one_err() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("a", Inner::A);
assert!(e.into_result(()).is_err());
}
#[test]
fn test_into_result_many_err() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("a", Inner::A);
e.push("b", Inner::A);
assert!(e.into_result(()).is_err());
}
#[test]
fn test_source_none() {
let e = ManyErrors::<&str, Inner>::new();
assert!(e.source().is_none());
}
#[test]
fn test_source_one_leaf_is_none() {
let mut e = ManyErrors::<&str, Mid>::new();
e.push("ctx", Mid::Inner(Inner::A));
assert!(e.source().is_none());
}
#[test]
fn test_source_one_group_is_none() {
let mut e = ManyErrors::<&str, Inner>::new();
let mut sub = ManyErrors::new();
sub.push("x", Inner::A);
e.push_group("g", sub);
assert!(e.source().is_none());
}
#[test]
fn test_source_many_is_none() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("a", Inner::A);
e.push("b", Inner::A);
assert!(e.source().is_none());
}
#[test]
fn test_debug_none() {
let e = ManyErrors::<&str, Inner>::new();
assert_eq!(format!("{e:?}"), "None");
}
#[test]
fn test_debug_one_is_tuple() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("ctx", Inner::A);
assert_eq!(
format!("{e:?}"),
r#"One(Leaf(WithContext { context: "ctx", error: A, .. }))"#
);
}
#[test]
fn test_debug_many_is_bare_list() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("a", Inner::A);
e.push("b", Inner::B);
assert_eq!(
format!("{e:?}"),
r#"[Leaf(WithContext { context: "a", error: A, .. }), Leaf(WithContext { context: "b", error: B, .. })]"#
);
}
#[test]
fn test_with_formats_none_stays_none() {
let e = ManyErrors::<&str, Inner>::new();
let swapped: ManyErrors<&str, Inner, &str, crate::tests::WcArrow> = e.with_formats();
assert!(matches!(swapped, ManyErrors::None));
}
#[test]
fn test_display_empty() {
let e = ManyErrors::<&str, Inner>::new();
assert_eq!(e.to_string(), "no errors");
}
#[test]
fn test_display_single_leaf_no_header() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("ctx", Inner::A);
assert_eq!(e.to_string(), "ctx: InnerA");
}
#[test]
fn test_display_two_leaves() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("a", Inner::A);
e.push("b", Inner::B);
assert_eq!(e.to_string(), "2 errors: a: InnerA; b: InnerB");
}
#[test]
fn test_display_does_not_walk_source() {
let mut e = ManyErrors::<&str, Mid>::new();
e.push("a", Mid::Inner(Inner::A));
e.push("b", Mid::Inner(Inner::B));
let s = e.to_string();
assert_eq!(s, "2 errors: a: mid; b: mid");
assert!(!s.contains("InnerA"), "source must not be walked: {s}");
}
#[test]
fn test_display_nested_group() {
let mut inner = ManyErrors::<&str, Inner>::new();
inner.push("x", Inner::A);
inner.push("y", Inner::B);
let mut outer = ManyErrors::<&str, Inner>::new();
outer.push("leaf", Inner::A);
outer.push_group("region", inner);
assert_eq!(
outer.to_string(),
"2 errors: leaf: InnerA; region (2 errors: x: InnerA; y: InnerB)"
);
}
#[test]
fn test_one_line_single_leaf_walks_chain() {
let mut e = ManyErrors::<&str, Mid>::new();
e.push("ctx", Mid::Inner(Inner::A));
assert_eq!(e.joined().to_string(), "ctx: mid: InnerA");
}
}