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
use crate::*;

/// The evaluation of a [MockFn].
///
/// Used to tell trait implementations whether to do perform their own evaluation of a call.
///
/// The output is generic, because both owned and referenced output are supported.
pub enum Evaluation<'i, O, F: MockFn> {
    /// Function evaluated to its output.
    Evaluated(O),
    /// Function not yet evaluated.
    Skipped(<F as MockInputs<'i>>::Inputs),
}

impl<'i, O, F: MockFn> Evaluation<'i, O, F> {
    /// Unwrap the `Evaluated` variant, or panic.
    /// The unimock instance must be passed in order to register that an eventual panic happened.
    pub fn unwrap(self, unimock: &Unimock) -> O {
        match self {
            Self::Evaluated(output) => output,
            Self::Skipped(_) => panic!(
                "{}",
                unimock.prepare_panic(error::MockError::CannotUnmock { name: F::NAME })
            ),
        }
    }
}

/// Evaluate a [MockFn] given some inputs, to produce its output.
#[track_caller]
pub fn eval<'i, F>(
    unimock: &Unimock,
    inputs: <F as MockInputs<'i>>::Inputs,
) -> macro_api::Evaluation<'i, F::Output, F>
where
    F: MockFn + 'static,
    F::Output: Sized,
{
    unimock.handle_error(eval::EvalCtx::new::<F>(&unimock.shared_state).eval_sized(inputs))
}

/// Evaluate a [MockFn] given some inputs, to produce its output, where output is borrowed from `self`.
#[track_caller]
pub fn eval_borrowed<'u, 'i, F>(
    unimock: &'u Unimock,
    inputs: <F as MockInputs<'i>>::Inputs,
) -> macro_api::Evaluation<'i, &'u F::Output, F>
where
    F: MockFn + 'static,
{
    unimock.handle_error(
        eval::EvalCtx::new::<F>(&unimock.shared_state).eval_unsized_self_borrowed(inputs),
    )
}

/// Evaluate a [MockFn] given some inputs, to produce its output, where output is borrowed from a parameter that is not self.
#[track_caller]
pub fn eval_borrowed_param<'u, 'i, F>(
    unimock: &'u Unimock,
    inputs: <F as MockInputs<'i>>::Inputs,
) -> macro_api::Evaluation<'i, &'i F::Output, F>
where
    F: MockFn + 'static,
{
    unimock.handle_error(
        eval::EvalCtx::new::<F>(&unimock.shared_state)
            .eval_unsized_static_ref(inputs, error::Lender::Param),
    )
}

/// Evaluate a [MockFn] given some inputs, to produce its output, where output is a static reference to `F::Output`.
#[track_caller]
pub fn eval_static_ref<'i, F>(
    unimock: &Unimock,
    inputs: <F as MockInputs<'i>>::Inputs,
) -> macro_api::Evaluation<'i, &'static F::Output, F>
where
    F: MockFn + 'static,
{
    unimock.handle_error(
        eval::EvalCtx::new::<F>(&unimock.shared_state)
            .eval_unsized_static_ref(inputs, error::Lender::Static),
    )
}

/// Trait for computing the proper [std::fmt::Debug] representation of a value.
pub trait ProperDebug {
    /// Format a debug representation.
    fn unimock_try_debug(&self) -> String;
}

/// Fallback trait (using autoref specialization) for returning `"?"` when the implementing value does not implement [std::fmt::Debug].
pub trait NoDebug {
    /// Format a debug representation.
    fn unimock_try_debug(&self) -> String;
}

// Autoref specialization:
// https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md

impl<T: std::fmt::Debug> ProperDebug for T {
    fn unimock_try_debug(&self) -> String {
        format!("{:?}", self)
    }
}

impl<T> NoDebug for &T {
    fn unimock_try_debug(&self) -> String {
        "?".to_string()
    }
}

/// Take a vector of strings, comma separate and put within parentheses.
pub fn format_inputs(inputs: &[String]) -> String {
    let joined = inputs.join(", ");
    format!("({})", joined)
}

/// Convert any type implementing `AsRef<str>` to a `&str`.
pub fn as_str_ref<T>(input: &T) -> &str
where
    T: AsRef<str>,
{
    input.as_ref()
}