1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
use std::borrow::Cow;
use std::fmt;
use crate::core::{style, Format, FormattedFailure, Formatter, MatchFailure, Matcher};
enum WhyFormatReason<'a> {
Eager(Cow<'a, str>),
Lazy(Box<dyn FnOnce() -> Cow<'a, str> + 'a>),
}
impl<'a> fmt::Debug for WhyFormatReason<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Eager(reason) => f.debug_tuple("Eager").field(reason).finish(),
Self::Lazy(_) => f.debug_tuple("Lazy").finish(),
}
}
}
/// A formatter that prints a context string provided by [`why`] or [`why_lazy`].
#[derive(Debug)]
pub struct WhyFormat<'a> {
reason: WhyFormatReason<'a>,
}
impl<'a> WhyFormat<'a> {
/// Create a new [`WhyFormat`] from the given string.
pub fn new(reason: impl Into<Cow<'a, str>>) -> Self {
Self {
reason: WhyFormatReason::Eager(reason.into()),
}
}
/// Create a new [`WhyFormat`] from the given function, which will be called lazily if the
/// matcher fails.
pub fn lazy(reason: impl FnOnce() -> Cow<'a, str> + 'a) -> Self {
Self {
reason: WhyFormatReason::Lazy(Box::new(reason)),
}
}
}
impl<'a> Format for WhyFormat<'a> {
type Value = MatchFailure<FormattedFailure>;
fn fmt(self, f: &mut Formatter, value: Self::Value) -> crate::Result<()> {
f.set_style(style::info());
f.write_str(style::INFO_SYMBOL);
f.write_str(" ");
match self.reason {
WhyFormatReason::Eager(reason) => {
f.write_str(reason.as_ref());
}
WhyFormatReason::Lazy(func) => {
let reason = (func)();
f.write_str(reason.as_ref());
}
};
f.reset_style();
f.write_char('\n');
f.write_fmt(value);
Ok(())
}
}
/// Attaches a context string to a matcher that will appear in the failure output.
///
/// # Examples
///
/// ```
/// use xpct::{expect, why, be_ge};
///
/// let index = 2_i32;
///
/// expect!(index).to(why(
/// be_ge(0),
/// "indices must be positive"
/// ));
/// ```
pub fn why<'a, In, PosOut, NegOut>(
matcher: Matcher<'a, In, PosOut, NegOut>,
reason: impl Into<Cow<'a, str>>,
) -> Matcher<In, PosOut, NegOut>
where
In: 'a,
PosOut: 'a,
NegOut: 'a,
{
matcher.wrapped(WhyFormat::new(reason))
}
/// Attaches a lazily evaluated context string to a matcher that will appear in the failure output.
///
/// This serves the same purpose as [`why`], except it can be used when the context value would be
/// expensive to compute.
///
/// # Example
///
/// ```
/// use std::borrow::Cow;
/// use xpct::{expect, why_lazy, be_ge};
///
/// // Imagine this is expensive to compute.
/// fn expensive_context() -> Cow<'static, str> {
/// Cow::Borrowed("indices must be positive")
/// }
///
/// let index = 2_i32;
///
/// expect!(index).to(why_lazy(
/// be_ge(0),
/// expensive_context
/// ));
/// ```
pub fn why_lazy<'a, In, PosOut, NegOut>(
matcher: Matcher<'a, In, PosOut, NegOut>,
reason: impl FnOnce() -> Cow<'a, str> + 'a,
) -> Matcher<In, PosOut, NegOut>
where
In: 'a,
PosOut: 'a,
NegOut: 'a,
{
matcher.wrapped(WhyFormat::lazy(reason))
}