use core::{error::Error, fmt};
use crate::Format;
pub trait Suggest {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(())
}
}
impl<T: Suggest + ?Sized> Suggest for &T {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Suggest::fmt(*self, f)
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Suggestion;
impl<E: Error + Suggest + ?Sized> Format<E> for Suggestion {
fn fmt(error: &E, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Suggest::fmt(error, f)
}
}
#[cfg(test)]
mod tests {
use core::error::Error as _;
use std::io;
use crate::{
Add, FormatError,
separator::NewLine,
tests::{Error, Inner, Mid, NoHint},
};
#[test]
fn hint_variant_renders_message() {
assert_eq!(
Error::One.suggestion().to_string(),
"Try passing --help to see available options.",
);
assert_eq!(
Error::Three(io::Error::other("x")).suggestion().to_string(),
"Check that the file path exists and permissions are correct.",
);
}
#[test]
fn no_hint_variant_renders_empty_string() {
assert_eq!(Error::Two(Inner::A).suggestion().to_string(), "");
assert_eq!(
Error::Transparent(Mid::Inner(Inner::A))
.suggestion()
.to_string(),
""
);
}
#[test]
fn default_impl_writes_nothing() {
assert_eq!(NoHint.suggestion().to_string(), "");
}
#[test]
fn debug_of_formatted_suggestion_surfaces_error_and_strategy() {
assert_eq!(
format!("{:?}", Error::One.suggestion()),
"Formatted { error: One, format: Suggestion }"
);
assert_eq!(
format!("{:?}", NoHint.suggestion()),
"Formatted { error: NoHint, format: Suggestion }"
);
}
#[test]
fn one_line_and_suggestion_are_independent_strategies() {
let e = Error::One;
assert_eq!(e.one_line().to_string(), "One");
assert_eq!(
e.suggestion().to_string(),
"Try passing --help to see available options.",
);
assert_eq!(
e.formatted::<Add<crate::OneLine, Add<NewLine, crate::Suggestion>>>()
.to_string(),
"One\nTry passing --help to see available options.",
);
}
#[test]
fn suggestion_ignores_source_chain_depth() {
let with_source = Error::Two(Inner::A);
assert_eq!(with_source.one_line().to_string(), "Two: InnerA");
assert_eq!(with_source.suggestion().to_string(), "");
let with_chain = Error::Transparent(Mid::Inner(Inner::A));
assert_eq!(with_chain.one_line().to_string(), "mid: InnerA");
assert_eq!(with_chain.suggestion().to_string(), "");
}
#[test]
fn suggestion_fires_even_with_no_source() {
assert!(Error::One.source().is_none());
assert_ne!(Error::One.suggestion().to_string(), "");
}
#[test]
fn transparent_collapses_display_but_not_suggestion() {
let with_inner = Error::Transparent(Mid::Inner(Inner::A));
let with_io = Error::Transparent(Mid::Io(io::Error::other("io error")));
assert_eq!(with_inner.to_string(), "mid");
assert_eq!(with_io.to_string(), "io error");
assert_eq!(with_inner.suggestion().to_string(), "");
assert_eq!(with_io.suggestion().to_string(), "");
}
#[test]
fn double_transparent_display_collapses_suggestion_stays_at_outermost() {
let e = Error::Transparent(Mid::Io(io::Error::other("deep io")));
assert_eq!(e.to_string(), "deep io");
assert!(e.source().is_none());
assert_eq!(e.suggestion().to_string(), "");
assert_eq!(e.one_line().to_string(), "deep io");
}
#[test]
fn hint_and_no_hint_variants_coexist_in_same_type() {
assert_ne!(Error::One.suggestion().to_string(), "");
assert_eq!(Error::Two(Inner::A).suggestion().to_string(), "");
assert_ne!(
Error::Three(io::Error::other("x")).suggestion().to_string(),
"",
);
assert_eq!(
Error::Transparent(Mid::Inner(Inner::A))
.suggestion()
.to_string(),
"",
);
}
#[test]
fn suggest_blanket_impl_works_on_shared_ref() {
let e = Error::One;
let r: &Error = &e;
assert_eq!(
r.suggestion().to_string(),
"Try passing --help to see available options.",
);
let no: &NoHint = &NoHint;
assert_eq!(no.suggestion().to_string(), "");
}
}