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 add;
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_some(&mut self, context: C, option: Option<E>) {
if let Some(error) = option {
self.push(context, error);
}
}
pub fn push_result<T>(&mut self, context: C, result: Result<T, E>) {
if let Err(error) = result {
self.push(context, error);
}
}
pub fn push_group(&mut self, context: GC, errors: Self) {
self.push_node(Node::Group(Subgroup::new(context, errors)));
}
pub fn into_group(self, context: GC) -> Self {
let mut outer = Self::None;
outer.push_group(context, self);
outer
}
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 ok(self) -> Option<()> {
matches!(self, Self::None).then_some(())
}
pub fn err(self) -> Option<Self> {
match self {
Self::None => None,
other => Some(other),
}
}
pub fn map_err<E2>(self, mut f: impl FnMut(E) -> E2) -> ManyErrors<C, E2, GC> {
map_err_many(self, &mut f)
}
pub fn map_context<C2>(self, mut f: impl FnMut(C) -> C2) -> ManyErrors<C2, E, GC> {
map_context_many(self, &mut f)
}
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 one_line(&self) -> crate::Formatted<&Self, Joined> {
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
}
}
fn map_err_node<C, E, GC, F, GF, E2>(
node: Node<C, E, GC, F, GF>,
f: &mut impl FnMut(E) -> E2,
) -> Node<C, E2, GC> {
match node {
Node::Leaf(w) => Node::Leaf(WithContext::new(w.context, f(w.error))),
Node::Group(g) => Node::Group(Subgroup::new(g.context, map_err_many(*g.errors, f))),
}
}
fn map_err_many<C, E, GC, F, GF, E2>(
me: ManyErrors<C, E, GC, F, GF>,
f: &mut impl FnMut(E) -> E2,
) -> ManyErrors<C, E2, GC> {
match me {
ManyErrors::None => ManyErrors::None,
ManyErrors::One(n) => ManyErrors::One(map_err_node(n, f)),
ManyErrors::Many(v) => {
let mut out = Vec::with_capacity(v.len());
for n in v {
out.push(map_err_node(n, f));
}
ManyErrors::Many(out)
}
}
}
fn map_context_node<C, E, GC, F, GF, C2>(
node: Node<C, E, GC, F, GF>,
f: &mut impl FnMut(C) -> C2,
) -> Node<C2, E, GC> {
match node {
Node::Leaf(w) => Node::Leaf(WithContext::new(f(w.context), w.error)),
Node::Group(g) => Node::Group(Subgroup::new(g.context, map_context_many(*g.errors, f))),
}
}
fn map_context_many<C, E, GC, F, GF, C2>(
me: ManyErrors<C, E, GC, F, GF>,
f: &mut impl FnMut(C) -> C2,
) -> ManyErrors<C2, E, GC> {
match me {
ManyErrors::None => ManyErrors::None,
ManyErrors::One(n) => ManyErrors::One(map_context_node(n, f)),
ManyErrors::Many(v) => {
let mut out = Vec::with_capacity(v.len());
for n in v {
out.push(map_context_node(n, f));
}
ManyErrors::Many(out)
}
}
}
impl<C, E, GC, F, GF, T> From<(C, Result<T, E>)> for ManyErrors<C, E, GC, F, GF> {
fn from((context, result): (C, Result<T, E>)) -> Self {
match result {
Ok(_) => Self::None,
Err(error) => {
let mut me = Self::None;
me.push(context, error);
me
}
}
}
}
#[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.one_line().to_string(), "ctx: mid: InnerA");
assert_eq!(e.joined().to_string(), e.one_line().to_string());
}
#[test]
fn test_one_line_shadows_format_error_trait() {
use crate::FormatError;
let mut e = ManyErrors::<&str, Mid>::new();
e.push("a", Mid::Inner(Inner::A));
e.push("b", Mid::Inner(Inner::B));
assert_eq!(
e.one_line().to_string(),
"2 errors: a: mid: InnerA; b: mid: InnerB"
);
assert_eq!(
FormatError::one_line(&e).to_string(),
"2 errors: a: mid; b: mid"
);
}
#[test]
fn test_ok_empty_is_some() {
let e = ManyErrors::<&str, Inner>::new();
assert_eq!(e.ok(), Some(()));
}
#[test]
fn test_ok_with_errors_is_none() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("a", Inner::A);
assert_eq!(e.ok(), None);
}
#[test]
fn test_err_empty_is_none() {
let e = ManyErrors::<&str, Inner>::new();
assert!(e.err().is_none());
}
#[test]
fn test_err_with_errors_is_some() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("a", Inner::A);
let errs = e.err().expect("should be Some");
assert_eq!(errs.len(), 1);
}
#[test]
fn test_map_err_none_stays_none() {
let e = ManyErrors::<&str, Inner>::new();
let mapped: ManyErrors<&str, String> = e.map_err(|err| err.to_string());
assert!(mapped.is_empty());
}
#[test]
fn test_map_err_transforms_leaves() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("a", Inner::A);
e.push("b", Inner::B);
let mapped = e.map_err(Mid::Inner);
assert_eq!(mapped.len(), 2);
assert_eq!(mapped.to_string(), "2 errors: a: mid; b: mid");
}
#[test]
fn test_map_err_recurses_into_groups() {
let mut inner = ManyErrors::<&str, Inner>::new();
inner.push("x", Inner::A);
let mut outer = ManyErrors::<&str, Inner>::new();
outer.push("leaf", Inner::B);
outer.push_group("region", inner);
let mapped = outer.map_err(Mid::Inner);
assert_eq!(mapped.len(), 2);
assert_eq!(mapped.to_string(), "2 errors: leaf: mid; region (x: mid)");
}
#[test]
fn test_map_context_none_stays_none() {
let e = ManyErrors::<&str, Inner>::new();
let mapped: ManyErrors<String, Inner, &str> = e.map_context(|ctx| ctx.to_uppercase());
assert!(mapped.is_empty());
}
#[test]
fn test_map_context_transforms_leaves() {
let mut e = ManyErrors::<&str, Inner>::new();
e.push("a", Inner::A);
e.push("b", Inner::B);
let mapped = e.map_context(|ctx| ctx.to_uppercase());
assert_eq!(mapped.len(), 2);
assert_eq!(mapped.to_string(), "2 errors: A: InnerA; B: InnerB");
}
#[test]
fn test_map_context_recurses_into_groups_leaves_only() {
let mut inner = ManyErrors::<&str, Inner>::new();
inner.push("x", Inner::A);
let mut outer = ManyErrors::<&str, Inner>::new();
outer.push("leaf", Inner::B);
outer.push_group("region", inner);
let mapped = outer.map_context(|ctx| ctx.to_uppercase());
assert_eq!(
mapped.to_string(),
"2 errors: LEAF: InnerB; region (X: InnerA)"
);
}
#[test]
fn test_from_result_err_produces_one_leaf() {
let e: ManyErrors<&str, Inner> = ("ctx", Err::<(), _>(Inner::A)).into();
assert_eq!(e.len(), 1);
}
#[test]
fn test_from_result_ok_produces_empty() {
let e: ManyErrors<&str, Inner> = ("ctx", Ok::<(), Inner>(())).into();
assert!(e.is_empty());
}
}