use core::{fmt, marker::PhantomData};
use crate::Format;
#[derive(Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Add<L, R>(PhantomData<fn() -> (L, R)>);
impl<L: fmt::Debug + Default, R: fmt::Debug + Default> fmt::Debug for Add<L, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Add")
.field(&L::default())
.field(&R::default())
.finish()
}
}
impl<E, L, R> Format<E> for Add<L, R>
where
E: ?Sized,
L: Format<E>,
R: Format<E>,
{
fn fmt(error: &E, f: &mut fmt::Formatter<'_>) -> fmt::Result {
L::fmt(error, f)?;
R::fmt(error, f)
}
}
pub mod separator {
use crate::{Add, Format};
use core::fmt;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NewLine;
impl<E: ?Sized> Format<E> for NewLine {
fn fmt(_: &E, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("\n")
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Space;
impl<E: ?Sized> Format<E> for Space {
fn fmt(_: &E, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(" ")
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Empty;
impl<E: ?Sized> Format<E> for Empty {
fn fmt(_: &E, _: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(())
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Colon;
impl<E: ?Sized> Format<E> for Colon {
fn fmt(_: &E, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(":")
}
}
pub type ColonSpace = Add<Colon, Space>;
pub type WithSep<L, Sep, R> = Add<Add<L, Sep>, R>;
pub type WithSpace<L, R> = WithSep<L, Space, R>;
pub type WithNewLine<L, R> = WithSep<L, NewLine, R>;
pub type WithColonSpace<L, R> = WithSep<L, ColonSpace, R>;
}
#[cfg(test)]
mod tests {
use core::error::Error;
use thiserror::Error;
use super::*;
use crate::{Formatted, OneLine, Suggest, Suggestion, Tree, tests::ErrorInner};
use separator::*;
#[derive(Error, Debug)]
enum SugError {
#[error("env file missing")]
NoEnv,
#[error("something else")]
Other,
}
impl Suggest for SugError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoEnv => f.write_str("Did you mean rename the .env.example file to .env?"),
Self::Other => Ok(()),
}
}
}
fn _assert_traits() {
fn assert_all<
T: Clone + Copy + Default + PartialEq + Eq + core::hash::Hash + Send + Sync,
>() {
}
assert_all::<Add<OneLine, separator::NewLine>>();
assert_all::<Add<Add<OneLine, separator::NewLine>, Suggestion>>();
assert_all::<separator::NewLine>();
assert_all::<separator::Space>();
fn assert_format<E: ?Sized, F: Format<E>>() {}
assert_format::<crate::tests::Error, Add<OneLine, separator::NewLine>>();
assert_format::<crate::tests::Error, Add<OneLine, Tree>>();
assert_format::<SugError, Add<Add<OneLine, separator::NewLine>, Suggestion>>();
fn assert_oneline<E: Error + ?Sized>()
where
OneLine: Format<E>,
{
}
assert_oneline::<crate::tests::Error>();
}
#[test]
fn test_one_line_plus_newline() {
let error = crate::tests::Error::Two(ErrorInner::One);
assert_eq!(
Formatted::<_, Add<OneLine, NewLine>>::new(error).to_string(),
"Two: One\n"
);
}
#[test]
fn test_nested_oneline_newline_suggestion() {
let error = SugError::NoEnv;
assert_eq!(
Formatted::<_, Add<Add<OneLine, NewLine>, Suggestion>>::new(error).to_string(),
"env file missing\nDid you mean rename the .env.example file to .env?"
);
}
#[test]
fn test_empty_rhs_keeps_separator() {
let error = SugError::Other;
assert_eq!(
Formatted::<_, Add<Add<OneLine, NewLine>, Suggestion>>::new(error).to_string(),
"something else\n"
);
}
#[test]
fn test_right_associated_nesting() {
let error = crate::tests::Error::Two(ErrorInner::One);
assert_eq!(
Formatted::<_, Add<OneLine, Add<NewLine, OneLine>>>::new(error).to_string(),
"Two: One\nTwo: One"
);
}
#[test]
fn test_space_between_repeats() {
let error = crate::tests::Error::One;
assert_eq!(
Formatted::<_, Add<OneLine, Add<Space, OneLine>>>::new(error).to_string(),
"One One"
);
}
#[test]
fn test_debug_prints_inner() {
let add = Add::<OneLine, separator::NewLine>::default();
assert_eq!(format!("{add:?}"), "Add(OneLine, NewLine)");
}
#[test]
fn test_colon_space_alias() {
let error = crate::tests::Error::One;
assert_eq!(
Formatted::<_, Add<OneLine, Add<ColonSpace, OneLine>>>::new(error).to_string(),
"One: One"
);
}
#[test]
fn test_with_space_alias() {
use separator::WithSpace;
let error = crate::tests::Error::One;
assert_eq!(
Formatted::<_, WithSpace<OneLine, OneLine>>::new(error).to_string(),
"One One"
);
}
#[test]
fn test_with_newline_alias() {
use separator::WithNewLine;
let error = crate::tests::Error::Two(ErrorInner::One);
assert_eq!(
Formatted::<_, WithNewLine<OneLine, OneLine>>::new(error).to_string(),
"Two: One\nTwo: One"
);
}
#[test]
fn test_with_colon_space_alias() {
use separator::WithColonSpace;
let error = crate::tests::Error::One;
assert_eq!(
Formatted::<_, WithColonSpace<OneLine, OneLine>>::new(error).to_string(),
"One: One"
);
}
#[test]
fn test_add_sep_generic_alias() {
use separator::Colon;
let error = crate::tests::Error::One;
assert_eq!(
Formatted::<_, WithSep<OneLine, Colon, OneLine>>::new(error).to_string(),
"One:One"
);
}
}