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))
}