xpct/core/
result.rs

1use std::fmt;
2
3use super::{FormattedOutput, MatcherFormat};
4
5/// The result of a failed matcher.
6///
7/// If the `Pos` and `Neg` type parameters are the same, you can omit the second one.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum MatchFailure<Pos, Neg = Pos> {
10    /// We were expecting the matcher to succeed but it failed.
11    Pos(Pos),
12
13    /// We were expecting the matcher to fail but it succeeded.
14    Neg(Neg),
15}
16
17impl<Pos, Neg> From<MatchFailure<Pos, Neg>> for FormattedOutput
18where
19    Pos: Into<FormattedOutput>,
20    Neg: Into<FormattedOutput>,
21{
22    fn from(fail: MatchFailure<Pos, Neg>) -> Self {
23        match fail {
24            MatchFailure::Pos(fail) => fail.into(),
25            MatchFailure::Neg(fail) => fail.into(),
26        }
27    }
28}
29
30impl<Pos, Neg> MatchFailure<Pos, Neg> {
31    /// This is a [`MatchFailure::Pos`].
32    pub fn is_pos(&self) -> bool {
33        match self {
34            Self::Pos(_) => true,
35            Self::Neg(_) => false,
36        }
37    }
38
39    /// This is a [`MatchFailure::Neg`].
40    pub fn is_neg(&self) -> bool {
41        match self {
42            Self::Pos(_) => false,
43            Self::Neg(_) => true,
44        }
45    }
46}
47
48impl<T> MatchFailure<T, T> {
49    /// Unwrap the inner value and return a reference to it.
50    ///
51    /// When the types for the positive and negative cases are the same, you can use this method to
52    /// unwrap the value.
53    pub fn unwrap(&self) -> &T {
54        match self {
55            Self::Pos(value) => value,
56            Self::Neg(value) => value,
57        }
58    }
59
60    /// Consume this match failure and return the inner value.
61    ///
62    /// This does the same thing as [`unwrap`], but returns the inner value by value.
63    ///
64    /// [`unwrap`]: crate::core::MatchFailure::unwrap
65    pub fn into_inner(self) -> T {
66        match self {
67            Self::Pos(value) => value,
68            Self::Neg(value) => value,
69        }
70    }
71}
72
73/// A [`MatchFailure`] which has been formatted with a [`MatcherFormat`].
74///
75/// This type is similar to [`FormattedOutput`], except it is specific to formatted [`MatchFailure`]
76/// values and is also an [`Error`].
77///
78/// Values of this type can be converted into a more generic [`FormattedOutput`] via
79/// [`From`]/[`Into`].
80///
81/// [`Error`]: std::error::Error
82#[derive(Debug)]
83pub struct FormattedFailure {
84    inner: FormattedOutput,
85}
86
87impl FormattedFailure {
88    /// Create a new [`FormattedFailure`] by formatting `fail` with `format`.
89    pub fn new<Fmt, Pos, Neg>(fail: MatchFailure<Pos, Neg>, format: Fmt) -> crate::Result<Self>
90    where
91        Fmt: MatcherFormat<Pos = Pos, Neg = Neg>,
92    {
93        Ok(Self {
94            inner: FormattedOutput::new(fail, format)?,
95        })
96    }
97}
98
99impl From<FormattedFailure> for FormattedOutput {
100    fn from(fail: FormattedFailure) -> Self {
101        fail.inner
102    }
103}
104
105impl fmt::Display for FormattedFailure {
106    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107        self.inner.fmt(f)
108    }
109}
110
111impl std::error::Error for FormattedFailure {}
112
113/// The result of a failed assertion.
114#[derive(Debug)]
115pub struct AssertionFailure<Ctx> {
116    /// A generic context value to associate with the assertion.
117    ///
118    /// This value can be use to capture context about the assertion. The provided
119    /// [`DefaultAssertionFormat`] has a `Context` value of [`AssertionContext`], which captures
120    /// the current file name, line and column number, and the stringified expression passed to
121    /// [`expect!`].
122    ///
123    /// [`DefaultAssertionFormat`]: crate::core::DefaultAssertionFormat
124    /// [`AssertionContext`]: crate::core::AssertionContext
125    /// [`expect!`]: crate::expect
126    pub ctx: Ctx,
127
128    /// The error that caused this assertion to fail.
129    pub error: MatchError,
130}
131
132/// The outcome of a matcher, either `Succcess` or `Fail`.
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub enum MatchOutcome<Success, Fail> {
135    /// The matcher succeeded.
136    Success(Success),
137
138    /// The matcher failed.
139    Fail(Fail),
140}
141
142impl<Success, Fail> MatchOutcome<Success, Fail> {
143    /// This is a [`MatchOutcome::Success`].
144    pub fn is_success(&self) -> bool {
145        match self {
146            MatchOutcome::Success(_) => true,
147            MatchOutcome::Fail(_) => false,
148        }
149    }
150
151    /// This is a [`MatchOutcome::Fail`].
152    pub fn is_fail(&self) -> bool {
153        match self {
154            MatchOutcome::Success(_) => false,
155            MatchOutcome::Fail(_) => true,
156        }
157    }
158}
159
160impl<Success, Fail> From<MatchOutcome<Success, Fail>> for Result<Success, Fail> {
161    fn from(result: MatchOutcome<Success, Fail>) -> Self {
162        match result {
163            MatchOutcome::Success(success) => Ok(success),
164            MatchOutcome::Fail(fail) => Err(fail),
165        }
166    }
167}
168
169/// An error from a matcher, meaning it either failed or returned an error.
170#[derive(Debug)]
171pub enum MatchError {
172    /// The matcher failed.
173    ///
174    /// This can either mean it was expecting to succeed but instead failed, or it was expecting to
175    /// fail but instead succeeded.
176    Fail(FormattedFailure),
177
178    /// The matcher returned an unexpected error.
179    Err(crate::Error),
180}
181
182impl fmt::Display for MatchError {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        match self {
185            MatchError::Fail(fail) => fail.fmt(f),
186            MatchError::Err(error) => error.fmt(f),
187        }
188    }
189}
190
191impl std::error::Error for MatchError {
192    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
193        match self {
194            MatchError::Fail(_) => None,
195            MatchError::Err(error) => error.source(),
196        }
197    }
198}