asserting/spec/
mod.rs

1//! This is the core of the `asserting` crate.
2
3use crate::colored;
4use crate::expectations::satisfies;
5use crate::std::any;
6use crate::std::borrow::Cow;
7use crate::std::error::Error as StdError;
8use crate::std::fmt::{self, Debug, Display};
9use crate::std::format;
10use crate::std::ops::Deref;
11use crate::std::string::{String, ToString};
12use crate::std::vec;
13use crate::std::vec::Vec;
14#[cfg(feature = "panic")]
15use crate::std::{cell::RefCell, rc::Rc};
16
17/// Starts an assertion for the given subject or expression in the
18/// [`PanicOnFail`] mode.
19///
20/// It wraps the subject into a [`Spec`] and sets the name of the expression and
21/// the code location of the assertion in the [`Spec`]. On the [`Spec`] any
22/// assertion method that is implemented for the subject's type can be called.
23///
24/// Assertions started with `assert_that!` will panic on the first failing
25/// assertion.
26///
27/// # Example
28///
29/// ```
30/// use asserting::prelude::*;
31///
32/// assert_that!(7 * 6).is_equal_to(42);
33/// ```
34///
35/// This call of the macro expands to:
36///
37/// ```
38/// # use asserting::prelude::*;
39/// assert_that(7 * 6)
40///     .named("7 * 6")
41///     .located_at(Location { file: file!(), line: line!(), column: column!() })
42///     .is_equal_to(42);
43/// ```
44#[macro_export]
45macro_rules! assert_that {
46    ($subject:expr) => {
47        $crate::prelude::assert_that($subject)
48            .named(&stringify!($subject).replace("\n", " "))
49            .located_at($crate::prelude::Location {
50                file: file!(),
51                line: line!(),
52                column: column!(),
53            })
54    };
55}
56
57/// Starts an assertion for the given subject or expression in the
58/// [`CollectFailures`] mode.
59///
60/// It wraps the subject into a [`Spec`] and sets the name of the expression and
61/// the code location of the assertion in the [`Spec`]. On the [`Spec`] any
62/// assertion method that is implemented for the subject's type can be called.
63///
64/// Assertions started with `verify_that!` will collect [`AssertFailure`]s for
65/// all failing assertions. The collected failures can be queried by calling one
66/// of the methods [`failures`](Spec::failures) or
67/// [`display_failures`](Spec::display_failures) on the [`Spec`].
68///
69/// # Example
70///
71/// ```
72/// use asserting::prelude::*;
73/// let some_text = "vel tempor augue delenit".to_string();
74/// let failures = verify_that!(some_text)
75///     .starts_with("nibh")
76///     .ends_with("magna")
77///     .failures();
78///
79/// assert_that!(failures).has_length(2);
80/// ```
81///
82/// This call of the macro expands to:
83///
84/// ```
85/// # use asserting::prelude::*;
86/// # let some_text = "vel tempor augue delenit".to_string();
87/// let failures = verify_that(some_text)
88///     .named("some_text")
89///     .located_at(Location { file: file!(), line: line!(), column: column!() })
90///     .starts_with("nibh")
91///     .ends_with("magna")
92///     .failures();
93/// ```
94#[macro_export]
95macro_rules! verify_that {
96    ($subject:expr) => {
97        $crate::prelude::verify_that($subject)
98            .named(&stringify!($subject).replace("\n", " "))
99            .located_at($crate::prelude::Location {
100                file: file!(),
101                line: line!(),
102                column: column!(),
103            })
104    };
105}
106
107/// Starts an assertion for some piece of code in the [`PanicOnFail`] mode.
108///
109/// It takes a closure and wraps it into a [`Spec`]. On the [`Spec`] any
110/// assertion method that is implemented for closures can be called.
111///
112/// Assertions started with `assert_that_code!` will panic on the first failing
113/// assertion.
114///
115/// # Examples
116///
117/// ```
118/// use asserting::prelude::*;
119///
120/// fn divide(a: i32, b: i32) -> i32 {
121///     a / b
122/// }
123///
124/// assert_that_code!(|| { divide(7, 0); }).panics();
125///
126/// assert_that_code!(|| { divide(7, 0); })
127///     .panics_with_message("attempt to divide by zero");
128///
129/// assert_that_code!(|| { divide(7, 3); }).does_not_panic();
130/// ```
131#[cfg(feature = "panic")]
132#[cfg_attr(feature = "panic", macro_export)]
133#[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
134macro_rules! assert_that_code {
135    ($subject:expr) => {
136        $crate::prelude::assert_that_code($subject)
137            .named(&stringify!($subject).replace("\n", " "))
138            .located_at($crate::prelude::Location {
139                file: file!(),
140                line: line!(),
141                column: column!(),
142            })
143    };
144}
145
146/// Starts an assertion for some piece of code in the [`CollectFailures`] mode.
147///
148/// It takes a closure and wraps it into a [`Spec`]. On the [`Spec`] any
149/// assertion method that is implemented for closures can be called.
150///
151/// Assertions started with `verify_that_code!` will collect [`AssertFailure`]s
152/// for all failing assertions. The collected failures can be queried by calling
153/// one of the methods [`failures`](Spec::failures) or
154/// [`display_failures`](Spec::display_failures) on the [`Spec`].
155///
156/// # Examples
157///
158/// ```
159/// use asserting::prelude::*;
160///
161/// fn divide(a: i32, b: i32) -> i32 {
162///     a / b
163/// }
164///
165/// let failures = verify_that_code!(|| { divide(7, 3); })
166///     .does_not_panic()
167///     .failures();
168///
169/// assert_that!(failures).is_empty();
170///
171/// let failures = verify_that_code!(|| { divide(7, 0); })
172///     .does_not_panic()
173///     .display_failures();
174///
175/// assert_that!(failures).contains_exactly([
176///     r#"expected || { divide(7, 0); } to not panic, but did panic
177///   with message: "attempt to divide by zero"
178/// "#
179/// ]);
180///
181/// let failures = verify_that_code!(|| { divide(7, 0); })
182///     .panics_with_message("division by zero")
183///     .display_failures();
184///
185/// assert_that!(failures).contains_exactly([
186///     r#"expected || { divide(7, 0); } to panic with message "division by zero"
187///    but was: "attempt to divide by zero"
188///   expected: "division by zero"
189/// "#
190/// ]);
191/// ```
192#[cfg(feature = "panic")]
193#[cfg_attr(feature = "panic", macro_export)]
194#[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
195macro_rules! verify_that_code {
196    ($subject:expr) => {
197        $crate::prelude::verify_that_code($subject)
198            .named(&stringify!($subject).replace("\n", " "))
199            .located_at($crate::prelude::Location {
200                file: file!(),
201                line: line!(),
202                column: column!(),
203            })
204    };
205}
206
207/// Starts an assertion for the given subject or expression in the
208/// [`PanicOnFail`] mode.
209///
210/// It wraps the subject into a [`Spec`]. On the [`Spec`] any
211/// assertion method that is implemented for the subject's type can be called.
212///
213/// Assertions started with `assert_that()` will panic on the first failing
214/// assertion.
215///
216/// In comparison to using the macro [`assert_that!`](crate::assert_that)
217/// calling this function does not set a name for the expression and does not
218/// set the code location of the assertion. In failure messages, the generic word
219/// "subject" is used. To set a specific text for the expression, the method
220/// [`named`](Spec::named) must be called explicitly.
221///
222/// Note: It is not necessary to set the code location explicitly as this
223/// function is annotated with `#[track_caller]`.
224///
225/// # Examples
226///
227/// ```
228/// use asserting::prelude::*;
229///
230/// assert_that(7 * 6).is_equal_to(42);
231/// ```
232///
233/// or with setting a name for the expression:
234///
235/// ```
236/// use asserting::prelude::*;
237///
238/// assert_that(7 * 6)
239///     .named("7 * 6")
240///     .is_equal_to(42);
241/// ```
242#[track_caller]
243pub fn assert_that<'a, S>(subject: S) -> Spec<'a, S, PanicOnFail> {
244    #[cfg(not(feature = "colored"))]
245    {
246        Spec::new(subject, PanicOnFail)
247    }
248    #[cfg(feature = "colored")]
249    {
250        Spec::new(subject, PanicOnFail).with_configured_diff_format()
251    }
252}
253
254/// Starts an assertion for the given subject or expression in the
255/// [`CollectFailures`] mode.
256///
257/// It wraps the subject into a [`Spec`]. On the [`Spec`] any
258/// assertion method that is implemented for the subject's type can be called.
259///
260/// Assertions started with `verify_that()` will collect [`AssertFailure`]s
261/// for all failing assertions. The collected failures can be queried by calling
262/// one of the methods [`failures`](Spec::failures) or the
263/// [`display_failures`](Spec::display_failures) on the [`Spec`].
264///
265/// In comparison to using the macro [`verify_that!`](crate::verify_that) calling
266/// this function does not set a name for the expression and does not set the
267/// code location of the assertion. In failure messages, the generic word
268/// "subject" is used. To set a specific text for the expression, the method
269/// [`named`](Spec::named) must be called explicitly.
270///
271/// # Examples
272///
273/// ```
274/// use asserting::prelude::*;
275///
276/// let some_text = "vel tempor augue delenit".to_string();
277///
278/// let failures = verify_that(some_text).named("my_thing")
279///     .starts_with("nibh")
280///     .ends_with("magna")
281///     .failures();
282///
283/// assert_that!(failures).has_length(2);
284/// ```
285///
286/// or with querying the failures as formated text:
287///
288/// ```
289/// use asserting::prelude::*;
290///
291/// let some_text = "vel tempor augue delenit".to_string();
292///
293/// let failures = verify_that(some_text).named("my_thing")
294///     .starts_with("nibh")
295///     .ends_with("magna")
296///     .display_failures();
297///
298/// assert_that!(failures).contains_exactly([
299///     r#"expected my_thing to start with "nibh"
300///    but was: "vel tempor augue delenit"
301///   expected: "nibh"
302/// "#,
303///     r#"expected my_thing to end with "magna"
304///    but was: "vel tempor augue delenit"
305///   expected: "magna"
306/// "#,
307/// ]);
308/// ```
309#[track_caller]
310pub fn verify_that<'a, S>(subject: S) -> Spec<'a, S, CollectFailures> {
311    Spec::new(subject, CollectFailures)
312}
313
314/// Starts an assertion for some piece of code in the [`PanicOnFail`] mode.
315///
316/// It takes a closure and wraps it into a [`Spec`]. On the [`Spec`] any
317/// assertion method that is implemented for closures can be called.
318///
319/// Assertions started with `assert_that_code()` will panic on the first failing
320/// assertion.
321///
322/// In comparison to using the macro [`assert_that_code!`](crate::assert_that_code)
323/// calling this function does not set a name for the expression and does not
324/// set the code location of the assertion. In failure messages, the generic
325/// word "the closure" is used. To set a specific text for the expression, the
326/// method [`named`](Spec::named) must be called explicitly.
327///
328/// # Examples
329///
330/// ```
331/// use asserting::prelude::*;
332///
333/// fn divide(a: i32, b: i32) -> i32 {
334///     a / b
335/// }
336///
337/// assert_that_code(|| { divide(7, 0); })
338///     .panics_with_message("attempt to divide by zero");
339///
340/// assert_that_code(|| { divide(7, 3); }).does_not_panic();
341/// ```
342#[cfg(feature = "panic")]
343#[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
344pub fn assert_that_code<'a, S>(code: S) -> Spec<'a, Code<S>, PanicOnFail>
345where
346    S: FnOnce(),
347{
348    #[cfg(not(feature = "colored"))]
349    {
350        Spec::new(Code::from(code), PanicOnFail).named("the closure")
351    }
352    #[cfg(feature = "colored")]
353    {
354        Spec::new(Code::from(code), PanicOnFail)
355            .named("the closure")
356            .with_configured_diff_format()
357    }
358}
359
360/// Starts an assertion for some piece of code in the [`CollectFailures`] mode.
361///
362/// It takes a closure and wraps it into a [`Spec`]. On the [`Spec`] any
363/// assertion method that is implemented for closures can be called.
364///
365/// Assertions started with `verify_that_code()` will collect [`AssertFailure`]s
366/// for all failing assertions. The collected failures can be queried by calling
367/// one of the methods [`failures`](Spec::failures) or
368/// [`display_failures`](Spec::display_failures) on the [`Spec`].
369///
370/// In comparison to using the macro [`verify_that_code!`](crate::verify_that_code)
371/// calling this function does not set a name for the expression and does not
372/// set the code location of the assertion. In failure messages, the generic
373/// word "the closure" is used. To set a specific text for the expression, the
374/// method [`named`](Spec::named) must be called explicitly.
375///
376/// # Examples
377///
378/// ```
379/// use asserting::prelude::*;
380///
381/// fn divide(a: i32, b: i32) -> i32 {
382///     a / b
383/// }
384///
385/// let failures = verify_that_code(|| { divide(7, 3); })
386///     .does_not_panic()
387///     .failures();
388///
389/// assert_that!(failures).is_empty();
390///
391/// let failures = verify_that_code(|| { divide(7, 0); })
392///     .does_not_panic()
393///     .display_failures();
394///
395/// assert_that!(failures).contains_exactly([
396///     r#"expected the closure to not panic, but did panic
397///   with message: "attempt to divide by zero"
398/// "#
399/// ]);
400///
401/// let failures = verify_that_code(|| { divide(7, 0); })
402///     .panics_with_message("division by zero")
403///     .display_failures();
404///
405/// assert_that!(failures).contains_exactly([
406///     r#"expected the closure to panic with message "division by zero"
407///    but was: "attempt to divide by zero"
408///   expected: "division by zero"
409/// "#
410/// ]);
411/// ```
412#[cfg(feature = "panic")]
413#[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
414pub fn verify_that_code<'a, S>(code: S) -> Spec<'a, Code<S>, CollectFailures>
415where
416    S: FnOnce(),
417{
418    Spec::new(Code::from(code), CollectFailures).named("the closure")
419}
420
421/// An expectation defines a test for a property of the asserted subject.
422///
423/// It requires two methods: a `test()` method and a `message()` method.
424/// The `test()` method is called to verify whether an actual subject meets the
425/// expected property. In case the test of the expectation fails, the
426/// `message()` method is called to form an expectation-specific failure
427/// message.
428pub trait Expectation<S: ?Sized> {
429    /// Verifies whether the actual subject fulfills the expected property.
430    fn test(&mut self, subject: &S) -> bool;
431
432    /// Forms a failure message for this expectation.
433    fn message(
434        &self,
435        expression: &Expression<'_>,
436        actual: &S,
437        inverted: bool,
438        format: &DiffFormat,
439    ) -> String;
440}
441
442/// Marks an expectation that it can be inverted by using the [`Not`]
443/// combinator.
444///
445/// An expectation is any type that implements the [`Expectation`] trait.
446///
447/// This trait is meant to be implemented in combination with the
448/// [`Expectation`] trait. It should only be implemented for an expectation if
449/// the inverted test is unmistakably meaningful, and if the failure message
450/// clearly states whether the expectation has been inverted or not.
451///
452/// [`Not`]: crate::expectations::Not
453pub trait Invertible {}
454
455/// A textual representation of the expression or subject that is being
456/// asserted.
457#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
458pub struct Expression<'a>(pub Cow<'a, str>);
459
460impl Default for Expression<'_> {
461    fn default() -> Self {
462        Self("subject".into())
463    }
464}
465
466impl Display for Expression<'_> {
467    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
468        write!(f, "{}", self.0)
469    }
470}
471
472impl Deref for Expression<'_> {
473    type Target = str;
474
475    fn deref(&self) -> &Self::Target {
476        &self.0
477    }
478}
479
480impl<'a> From<&'a str> for Expression<'a> {
481    fn from(s: &'a str) -> Self {
482        Self(s.into())
483    }
484}
485
486impl From<String> for Expression<'_> {
487    fn from(s: String) -> Self {
488        Self(s.into())
489    }
490}
491
492/// The location of an assertion in the source code respectively test code.
493///
494/// # Related
495/// - [`core::panic::Location`]
496#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
497pub struct Location<'a> {
498    /// The file path of the source file where the assertion is located.
499    pub file: &'a str,
500
501    /// The line number within the source file where the assertion is located.
502    pub line: u32,
503
504    /// The column number on the line within the source file where the assertion
505    /// is located.
506    pub column: u32,
507}
508
509impl Display for Location<'_> {
510    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
511        #[cfg(not(test))]
512        let file = self.file;
513        #[cfg(test)]
514        let file = self.file.replace('\\', "/");
515        write!(f, "{file}:{}:{}", self.line, self.column)
516    }
517}
518
519impl<'a> Location<'a> {
520    /// Constructs a new `Location` with the given file, line and column.
521    #[must_use]
522    pub const fn new(file: &'a str, line: u32, column: u32) -> Self {
523        Self { file, line, column }
524    }
525}
526
527impl Location<'_> {
528    /// Returns the file path of this location.
529    pub fn file(&self) -> &str {
530        self.file
531    }
532
533    /// Returns the line number of this location.
534    pub fn line(&self) -> u32 {
535        self.line
536    }
537
538    /// Returns the column number of this location.
539    pub fn column(&self) -> u32 {
540        self.column
541    }
542}
543
544/// An owned location in the source code respectively test code.
545///
546/// It is basically the same as [`Location`] but uses owned types instead of
547/// borrowed types for its fields.
548#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
549pub struct OwnedLocation {
550    /// The file path of the source file where the assertion is located.
551    pub file: String,
552
553    /// The line number within the source file where the assertion is located.
554    pub line: u32,
555
556    /// The column number on the line within the source file where the assertion
557    /// is located.
558    pub column: u32,
559}
560
561impl Display for OwnedLocation {
562    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563        #[cfg(not(test))]
564        let file = self.file.clone();
565        #[cfg(test)]
566        let file = self.file.replace('\\', "/");
567        write!(f, "{file}:{}:{}", self.line, self.column)
568    }
569}
570
571impl OwnedLocation {
572    /// Constructs a new `OwnedLocation` with the given file, line and column.
573    #[must_use]
574    pub fn new(file: impl Into<String>, line: u32, column: u32) -> Self {
575        Self {
576            file: file.into(),
577            line,
578            column,
579        }
580    }
581
582    /// Reference this [`OwnedLocation`] as a [`Location`].
583    pub fn as_location(&self) -> Location<'_> {
584        Location {
585            file: &self.file,
586            line: self.line,
587            column: self.column,
588        }
589    }
590}
591
592impl From<Location<'_>> for OwnedLocation {
593    fn from(value: Location<'_>) -> Self {
594        Self {
595            file: value.file.into(),
596            line: value.line,
597            column: value.column,
598        }
599    }
600}
601
602impl OwnedLocation {
603    /// Returns the file path of this location.
604    pub fn file(&self) -> &str {
605        &self.file
606    }
607
608    /// Returns the line number of this location.
609    pub fn line(&self) -> u32 {
610        self.line
611    }
612
613    /// Returns the column number of this location.
614    pub fn column(&self) -> u32 {
615        self.column
616    }
617}
618
619/// Data of an actual assertion.
620///
621/// It holds the data needed to execute an assertion such as the subject,
622/// the name of the subject or expression, an optional description of the
623/// current assertion and the location of the assertion in the source code
624/// respectively test code.
625///
626/// It also holds the concrete [`FailingStrategy`] on how to behave in case
627/// an assertion fails.
628///
629/// In case of the [`CollectFailures`] failing strategy, the [`AssertFailure`]s
630/// are collected in this struct.
631pub struct Spec<'a, S, R> {
632    subject: S,
633    expression: Option<Expression<'a>>,
634    description: Option<Cow<'a, str>>,
635    location: Option<Location<'a>>,
636    failures: Vec<AssertFailure>,
637    diff_format: DiffFormat,
638    failing_strategy: R,
639}
640
641impl<S, R> Spec<'_, S, R> {
642    /// Returns the subject.
643    pub fn subject(&self) -> &S {
644        &self.subject
645    }
646
647    /// Returns the expression (or subject name) if one has been set.
648    pub fn expression(&self) -> Option<&Expression<'_>> {
649        self.expression.as_ref()
650    }
651
652    /// Returns the location in source code or test code if it has been set.
653    pub fn location(&self) -> Option<Location<'_>> {
654        self.location
655    }
656
657    /// Returns the description or the assertion if it has been set.
658    pub fn description(&self) -> Option<&str> {
659        self.description.as_deref()
660    }
661
662    /// Returns the diff format used with this assertion.
663    pub const fn diff_format(&self) -> &DiffFormat {
664        &self.diff_format
665    }
666
667    /// Returns the failing strategy that is used in case an assertion fails.
668    pub fn failing_strategy(&self) -> &R {
669        &self.failing_strategy
670    }
671
672    /// Returns whether there are assertion failures collected so far.
673    pub fn has_failures(&self) -> bool {
674        !self.failures.is_empty()
675    }
676
677    /// Returns the assertion failures that have been collected so far.
678    pub fn failures(&self) -> Vec<AssertFailure> {
679        self.failures.clone()
680    }
681
682    /// Returns the assertion failures collected so far as formatted text.
683    pub fn display_failures(&self) -> Vec<String> {
684        self.failures.iter().map(ToString::to_string).collect()
685    }
686}
687
688impl<'a, S, R> Spec<'a, S, R> {
689    /// Constructs a new `Spec` for the given subject and with the specified
690    /// failing strategy.
691    ///
692    /// The diff format is set to "no highlighting". Failure messages will not
693    /// highlight differences between the actual and the expected value.
694    #[must_use = "a spec does nothing unless an assertion method is called"]
695    pub const fn new(subject: S, failing_strategy: R) -> Self {
696        Self {
697            subject,
698            expression: None,
699            description: None,
700            location: None,
701            failures: vec![],
702            diff_format: colored::DIFF_FORMAT_NO_HIGHLIGHT,
703            failing_strategy,
704        }
705    }
706
707    /// Sets the subject name or expression for this assertion.
708    #[must_use = "a spec does nothing unless an assertion method is called"]
709    pub fn named(mut self, subject_name: impl Into<Cow<'a, str>>) -> Self {
710        self.expression = Some(Expression(subject_name.into()));
711        self
712    }
713
714    /// Sets a custom description about what is being asserted.
715    #[must_use = "a spec does nothing unless an assertion method is called"]
716    pub fn described_as(mut self, description: impl Into<Cow<'a, str>>) -> Self {
717        self.description = Some(description.into());
718        self
719    }
720
721    /// Sets the location of the assertion in the source code respectively test
722    /// code.
723    #[must_use = "a spec does nothing unless an assertion method is called"]
724    pub const fn located_at(mut self, location: Location<'a>) -> Self {
725        self.location = Some(location);
726        self
727    }
728
729    /// Sets the diff format that is used to highlight differences between
730    /// the actual value and the expected value.
731    ///
732    /// Note: This method must be called before an assertion method is called to
733    /// have an effect on the failure message of the assertion as failure
734    /// messages are formatted immediately when an assertion is executed.
735    #[must_use = "a spec does nothing unless an assertion method is called"]
736    pub const fn with_diff_format(mut self, diff_format: DiffFormat) -> Self {
737        self.diff_format = diff_format;
738        self
739    }
740
741    /// Sets the diff format used to highlight differences between the actual
742    /// value and the expected value according the configured mode.
743    ///
744    /// The mode is configured via environment variables like described in the
745    /// module [colored].
746    #[cfg(feature = "colored")]
747    #[cfg_attr(docsrs, doc(cfg(feature = "colored")))]
748    #[must_use = "a spec does nothing unless an assertion method is called"]
749    pub fn with_configured_diff_format(self) -> Self {
750        use crate::colored::configured_diff_format;
751        #[cfg(not(feature = "std"))]
752        {
753            self.with_diff_format(configured_diff_format())
754        }
755        #[cfg(feature = "std")]
756        {
757            use crate::std::sync::OnceLock;
758            static DIFF_FORMAT: OnceLock<DiffFormat> = OnceLock::new();
759            let diff_format = DIFF_FORMAT.get_or_init(configured_diff_format);
760            self.with_diff_format(diff_format.clone())
761        }
762    }
763
764    /// Maps the current subject to some other value.
765    ///
766    /// It takes a closure that maps the current subject to a new subject and
767    /// returns a new `Spec` with the value returned by the closure as the new
768    /// subject. The new subject may have a different type than the original
769    /// subject. All other data like expression, description and location are
770    /// taken over from this `Spec` into the returned `Spec`.
771    ///
772    /// This function is useful when having a custom type and some specific
773    /// property of this type shall be asserted only.
774    ///
775    /// This is an alias function to the [`mapping()`](Spec::mapping) function.
776    /// Both functions do exactly the same. The idea is to provide different
777    /// names to be able to express the intent more clearly when used in
778    /// assertions.
779    ///
780    /// # Example
781    ///
782    /// ```
783    /// use asserting::prelude::*;
784    ///
785    /// struct MyStruct {
786    ///     important_property: String,
787    ///     other_property: f64,
788    /// }
789    ///
790    /// let some_thing = MyStruct {
791    ///     important_property: "imperdiet aliqua zzril eiusmod".into(),
792    ///     other_property: 99.9,
793    /// };
794    ///
795    /// assert_that!(some_thing).extracting(|s| s.important_property)
796    ///     .is_equal_to("imperdiet aliqua zzril eiusmod");
797    ///
798    /// ```
799    #[must_use = "a spec does nothing unless an assertion method is called"]
800    pub fn extracting<F, U>(self, extractor: F) -> Spec<'a, U, R>
801    where
802        F: FnOnce(S) -> U,
803    {
804        self.mapping(extractor)
805    }
806
807    /// Maps the current subject to some other value.
808    ///
809    /// It takes a closure that maps the current subject to a new subject and
810    /// returns a new `Spec` with the value returned by the closure as the new
811    /// subject. The new subject may have a different type than the original
812    /// subject. All other data like expression, description and location are
813    /// taken over from this `Spec` into the returned `Spec`.
814    ///
815    /// This function is useful if some type does not implement a trait that is
816    /// required for an assertion.
817    ///
818    /// `Spec` also provides the [`extracting()`](Spec::extracting) function,
819    /// which is an alias to this function. Both functions do exactly the same.
820    /// Choose that function of which its name expresses the intent more
821    /// clearly.
822    ///
823    /// # Example
824    ///
825    /// ```
826    /// use asserting::prelude::*;
827    ///
828    /// struct Point {
829    ///     x: i64,
830    ///     y: i64,
831    /// }
832    ///
833    /// let target = Point { x: 12, y: -64 };
834    ///
835    /// assert_that!(target).mapping(|s| (s.x, s.y)).is_equal_to((12, -64));
836    /// ```
837    ///
838    /// The custom type `Point` does not implement the `PartialEq` trait nor
839    /// the `Debug` trait, which are both required for an `is_equal_to`
840    /// assertion. So we map the subject of the type `Point` to a tuple of its
841    /// fields.
842    #[must_use = "a spec does nothing unless an assertion method is called"]
843    pub fn mapping<F, U>(self, mapper: F) -> Spec<'a, U, R>
844    where
845        F: FnOnce(S) -> U,
846    {
847        Spec {
848            subject: mapper(self.subject),
849            expression: self.expression,
850            description: self.description,
851            location: self.location,
852            failures: self.failures,
853            diff_format: self.diff_format,
854            failing_strategy: self.failing_strategy,
855        }
856    }
857}
858
859impl<S, R> Spec<'_, S, R>
860where
861    R: FailingStrategy,
862{
863    /// Asserts the given expectation.
864    ///
865    /// In case the expectation is not meet, it does fail according the current
866    /// failing strategy of this `Spec`.
867    ///
868    /// This method is called from the implementations of the assertion traits
869    /// defined in the [`assertions`](crate::assertions) module. Implementations
870    /// of custom assertions will call this method with a proper expectation.
871    ///
872    /// # Examples
873    ///
874    /// ```
875    /// use asserting::expectations::{IsEmpty, IsEqualTo};
876    /// use asserting::prelude::*;
877    ///
878    /// assert_that!(7 * 6).expecting(IsEqualTo {expected: 42 });
879    ///
880    /// assert_that!("").expecting(IsEmpty);
881    /// ```
882    #[allow(clippy::needless_pass_by_value, clippy::return_self_not_must_use)]
883    #[track_caller]
884    pub fn expecting(mut self, mut expectation: impl Expectation<S>) -> Self {
885        if !expectation.test(&self.subject) {
886            let default_expression = Expression::default();
887            let expression = self.expression.as_ref().unwrap_or(&default_expression);
888            let message = expectation.message(expression, &self.subject, false, &self.diff_format);
889            self.do_fail_with_message(message);
890        }
891        self
892    }
893
894    /// Asserts whether the given predicate is meet.
895    ///
896    /// This method takes a predicate function and calls it as an expectation.
897    /// In case the predicate function returns false, it does fail with a
898    /// generic failure message and according to the current failing strategy of
899    /// this `Spec`.
900    ///
901    /// This method can be used to do simple custom assertions without
902    /// implementing an [`Expectation`] and an assertion trait.
903    ///
904    /// # Examples
905    ///
906    /// ```
907    /// use asserting::prelude::*;
908    ///
909    /// fn is_odd(value: &i32) -> bool {
910    ///     value & 1 == 1
911    /// }
912    ///
913    /// assert_that!(37).satisfies(is_odd);
914    ///
915    /// let failures = verify_that!(22).satisfies(is_odd).display_failures();
916    ///
917    /// assert_that!(failures).contains_exactly([
918    ///     "expected 22 to satisfy the given predicate, but returned false\n"
919    /// ]);
920    /// ```
921    ///
922    /// To assert a predicate with a custom failure message instead of the
923    /// generic one, use the method
924    /// [`satisfies_with_message`](Spec::satisfies_with_message).
925    #[allow(clippy::return_self_not_must_use)]
926    #[track_caller]
927    pub fn satisfies<P>(self, predicate: P) -> Self
928    where
929        P: Fn(&S) -> bool,
930    {
931        self.expecting(satisfies(predicate))
932    }
933
934    /// Asserts whether the given predicate is meet.
935    ///
936    /// This method takes a predicate function and calls it as an expectation.
937    /// In case the predicate function returns false, it does fail with the
938    /// provided failure message and according to the current failing strategy
939    /// of this `Spec`.
940    ///
941    /// This method can be used to do simple custom assertions without
942    /// implementing an [`Expectation`] and an assertion trait.
943    ///
944    /// # Examples
945    ///
946    /// ```
947    /// use asserting::prelude::*;
948    ///
949    /// fn is_odd(value: &i32) -> bool {
950    ///     value & 1 == 1
951    /// }
952    ///
953    /// assert_that!(37).satisfies_with_message("expected my number to be odd", is_odd);
954    ///
955    /// let failures = verify_that!(22)
956    ///         .satisfies_with_message("expected my number to be odd", is_odd)
957    ///         .display_failures();
958    ///
959    /// assert_that!(failures).contains_exactly([
960    ///     "expected my number to be odd\n"
961    /// ]);
962    /// ```
963    ///
964    /// To assert a predicate with a generic failure message instead of
965    /// providing one use the method
966    /// [`satisfies`](Spec::satisfies).
967    #[allow(clippy::return_self_not_must_use)]
968    #[track_caller]
969    pub fn satisfies_with_message<P>(self, message: impl Into<String>, predicate: P) -> Self
970    where
971        P: Fn(&S) -> bool,
972    {
973        self.expecting(satisfies(predicate).with_message(message))
974    }
975
976    /// Fails the assertion according the current failing strategy of this
977    /// `Spec`.
978    #[track_caller]
979    fn do_fail_with_message(&mut self, message: impl Into<String>) {
980        let message = message.into();
981        let failure = AssertFailure {
982            description: self.description.clone().map(String::from),
983            message,
984            location: self.location.map(OwnedLocation::from),
985        };
986        self.failures.push(failure);
987        self.failing_strategy.do_fail_with(&self.failures);
988    }
989}
990
991impl<S> Spec<'_, S, CollectFailures> {
992    /// Turns assertions into "soft assertions".
993    ///
994    /// It executes all specified assertions on a `Spec` and if at least one
995    /// assertion fails, it panics. The panic message contains the messages of
996    /// all assertions that have failed.
997    ///
998    /// This method is only available on `Spec`s with the
999    /// [`CollectFailures`]-[`FailingStrategy`]. That is any `Spec` contructed
1000    /// by the macros [`verify_that!`] and [`verify_that_code!`] or by the
1001    /// functions [`verify_that()`] and [`verify_that_code()`].
1002    ///
1003    /// On a `Spec` with the [`PanicOnFail`]-[`FailingStrategy`] it would not
1004    /// work as the very first failing assertion panics immediately, and later
1005    /// assertions never get executed.
1006    ///
1007    /// # Examples
1008    ///
1009    /// Running the following two assertions in "soft" mode:
1010    ///
1011    /// ```should_panic
1012    /// use asserting::prelude::*;
1013    ///
1014    /// verify_that!("the answer to all important questions is 42")
1015    ///     .contains("unimportant")
1016    ///     .has_at_most_length(41)
1017    ///     .soft_panic();
1018    /// ```
1019    ///
1020    /// executes both assertions and prints the messages of both failing
1021    /// assertions in the panic message:
1022    ///
1023    /// ```console
1024    /// expected subject to contain "unimportant"
1025    ///    but was: "the answer to all important questions is 42"
1026    ///   expected: "unimportant"
1027    ///
1028    /// expected subject to have at most a length of 41
1029    ///    but was: 43
1030    ///   expected: <= 41
1031    /// ```
1032    ///
1033    /// To highlight differences in failure messages of soft assertions use
1034    /// the `with_configured_diff_format()` method, like so:
1035    ///
1036    /// ```
1037    /// # #[cfg(not(feature = "colored"))]
1038    /// # fn main() {}
1039    /// # #[cfg(feature = "colored")]
1040    /// # fn main() {
1041    /// use asserting::prelude::*;
1042    ///
1043    /// verify_that!("the answer to all important questions is 42")
1044    ///     .with_configured_diff_format()
1045    ///     .contains("important")
1046    ///     .has_at_most_length(43)
1047    ///     .soft_panic();
1048    /// # }
1049    /// ```
1050    pub fn soft_panic(&self) {
1051        if !self.failures.is_empty() {
1052            PanicOnFail.do_fail_with(&self.failures);
1053        }
1054    }
1055}
1056
1057impl<'a, I, R> Spec<'a, I, R> {
1058    /// Iterates over the elements of a collection or an iterator and executes
1059    /// the given assertions for each of those elements. If all elements are
1060    /// asserted successfully, the whole assertion succeeds.
1061    ///
1062    /// It iterates over all elements of the collection or iterator and collects
1063    /// the failure messages for those elements where the assertion fails. In
1064    /// other words, it does not stop iterating when the assertion for one
1065    /// element fails.
1066    ///
1067    /// The failure messages contain the position of the element within the
1068    /// collection or iterator. The position is 0-based. So a failure message
1069    /// for the first element contains `[0]`, the second `[1]`, and so on.
1070    ///
1071    /// # Example
1072    ///
1073    /// The following assertion:
1074    ///
1075    /// ```should_panic
1076    /// use asserting::prelude::*;
1077    ///
1078    /// let numbers = [2, 4, 6, 8, 10];
1079    ///
1080    /// assert_that!(numbers).each_element(|e|
1081    ///     e.is_greater_than(2)
1082    ///         .is_at_most(7)
1083    /// );
1084    /// ```
1085    ///
1086    /// will print:
1087    ///
1088    /// ```console
1089    /// expected numbers [0] to be greater than 2
1090    ///    but was: 2
1091    ///   expected: > 2
1092    ///
1093    /// expected numbers [3] to be at most 7
1094    ///    but was: 8
1095    ///   expected: <= 7
1096    ///
1097    /// expected numbers [4] to be at most 7
1098    ///    but was: 10
1099    ///   expected: <= 7
1100    /// ```
1101    #[allow(clippy::return_self_not_must_use)]
1102    #[track_caller]
1103    pub fn each_element<T, A, B>(mut self, assert: A) -> Spec<'a, (), R>
1104    where
1105        I: IntoIterator<Item = T>,
1106        A: Fn(Spec<'a, T, CollectFailures>) -> Spec<'a, B, CollectFailures>,
1107    {
1108        let default_expression = &Expression::default();
1109        let root_expression = self.expression.as_ref().unwrap_or(default_expression);
1110        let mut position = -1;
1111        for item in self.subject {
1112            position += 1;
1113            let element_spec = Spec {
1114                subject: item,
1115                expression: Some(format!("{root_expression} [{position}]").into()),
1116                description: None,
1117                location: self.location,
1118                failures: vec![],
1119                diff_format: self.diff_format.clone(),
1120                failing_strategy: CollectFailures,
1121            };
1122            let failures = assert(element_spec).failures;
1123            self.failures.extend(failures);
1124        }
1125        if !self.failures.is_empty()
1126            && any::type_name_of_val(&self.failing_strategy) == any::type_name::<PanicOnFail>()
1127        {
1128            PanicOnFail.do_fail_with(&self.failures);
1129        }
1130        Spec {
1131            subject: (),
1132            expression: self.expression,
1133            description: self.description,
1134            location: self.location,
1135            failures: self.failures,
1136            diff_format: self.diff_format,
1137            failing_strategy: self.failing_strategy,
1138        }
1139    }
1140
1141    /// Iterates over the elements of a collection or an iterator and executes
1142    /// the given assertions for each of those elements. If the assertion of any
1143    /// element is successful, the iteration stops and the whole assertion
1144    /// succeeds.
1145    ///
1146    /// If the assertion fails for all elements, the failures of the assertion
1147    /// for all elements are collected.
1148    ///
1149    /// The failure messages contain the position of the element within the
1150    /// collection or iterator. The position is 0-based. So a failure message
1151    /// for the first element contains `[0]`, the second `[1]`, and so on.
1152    ///
1153    /// # Example
1154    ///
1155    /// The following assertion:
1156    ///
1157    /// ```should_panic
1158    /// use asserting::prelude::*;
1159    ///
1160    /// let digit_names = ["one", "two", "three"];
1161    ///
1162    /// assert_that!(digit_names).any_element(|e|
1163    ///     e.contains('x')
1164    /// );
1165    /// ```
1166    ///
1167    /// will print:
1168    ///
1169    /// ```console
1170    /// expected digit_names [0] to contain 'x'
1171    ///    but was: "one"
1172    ///   expected: 'x'
1173    ///
1174    /// expected digit_names [1] to contain 'x'
1175    ///    but was: "two"
1176    ///   expected: 'x'
1177    ///
1178    /// expected digit_names [2] to contain 'x'
1179    ///    but was: "three"
1180    ///   expected: 'x'
1181    /// ```
1182    #[track_caller]
1183    pub fn any_element<T, A, B>(mut self, assert: A) -> Spec<'a, (), R>
1184    where
1185        I: IntoIterator<Item = T>,
1186        A: Fn(Spec<'a, T, CollectFailures>) -> Spec<'a, B, CollectFailures>,
1187    {
1188        let default_expression = &Expression::default();
1189        let root_expression = self.expression.as_ref().unwrap_or(default_expression);
1190        let mut any_success = false;
1191        let mut position = -1;
1192        for item in self.subject {
1193            position += 1;
1194            let element_spec = Spec {
1195                subject: item,
1196                expression: Some(format!("{root_expression} [{position}]").into()),
1197                description: None,
1198                location: self.location,
1199                failures: vec![],
1200                diff_format: self.diff_format.clone(),
1201                failing_strategy: CollectFailures,
1202            };
1203            let failures = assert(element_spec).failures;
1204            if failures.is_empty() {
1205                any_success = true;
1206                break;
1207            }
1208            self.failures.extend(failures);
1209        }
1210        if !any_success
1211            && any::type_name_of_val(&self.failing_strategy) == any::type_name::<PanicOnFail>()
1212        {
1213            PanicOnFail.do_fail_with(&self.failures);
1214        }
1215        Spec {
1216            subject: (),
1217            expression: self.expression,
1218            description: self.description,
1219            location: self.location,
1220            failures: self.failures,
1221            diff_format: self.diff_format,
1222            failing_strategy: self.failing_strategy,
1223        }
1224    }
1225}
1226
1227/// An error describing a failed assertion.
1228///
1229/// This struct implements the [`std::error::Error`] trait.
1230#[derive(Debug, Clone, PartialEq, Eq)]
1231pub struct AssertFailure {
1232    description: Option<String>,
1233    message: String,
1234    location: Option<OwnedLocation>,
1235}
1236
1237impl Display for AssertFailure {
1238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1239        match &self.description {
1240            None => {
1241                writeln!(f, "{}", &self.message)?;
1242            },
1243            Some(description) => {
1244                writeln!(f, "{description}\n{}", &self.message)?;
1245            },
1246        }
1247        Ok(())
1248    }
1249}
1250
1251impl StdError for AssertFailure {}
1252
1253#[allow(clippy::must_use_candidate)]
1254impl AssertFailure {
1255    /// Returns the description of the assertion that failed.
1256    pub fn description(&self) -> Option<&String> {
1257        self.description.as_ref()
1258    }
1259
1260    /// Returns the failure message of the assertion that failed.
1261    #[allow(clippy::missing_const_for_fn)]
1262    pub fn message(&self) -> &str {
1263        &self.message
1264    }
1265
1266    /// Returns the location of the assertion in the source code / test code if
1267    /// it has been set in the [`Spec`].
1268    pub fn location(&self) -> Option<&OwnedLocation> {
1269        self.location.as_ref()
1270    }
1271}
1272
1273/// Start and end tag that marks a highlighted part of a string.
1274#[derive(Debug, Clone, PartialEq, Eq)]
1275pub(crate) struct Highlight {
1276    pub(crate) start: &'static str,
1277    pub(crate) end: &'static str,
1278}
1279
1280/// Definition of format properties for highlighting differences between two
1281/// values.
1282#[derive(Debug, Clone, PartialEq, Eq)]
1283pub struct DiffFormat {
1284    pub(crate) unexpected: Highlight,
1285    pub(crate) missing: Highlight,
1286}
1287
1288/// Defines the behavior when an assertion fails.
1289///
1290/// This crate provides two implementations:
1291///
1292/// * [`PanicOnFail`] - panics when an assertion fails
1293/// * [`CollectFailures`] - collects [`AssertFailure`]s of assertions that have failed.
1294pub trait FailingStrategy {
1295    /// Reacts to an assertion that has failed with the [`AssertFailure`]s given
1296    /// as argument.
1297    fn do_fail_with(&self, failures: &[AssertFailure]);
1298}
1299
1300/// [`FailingStrategy`] that panics when an assertion fails.
1301#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1302pub struct PanicOnFail;
1303
1304impl FailingStrategy for PanicOnFail {
1305    #[track_caller]
1306    fn do_fail_with(&self, failures: &[AssertFailure]) {
1307        let message = failures
1308            .iter()
1309            .map(ToString::to_string)
1310            .collect::<Vec<_>>()
1311            .join("\n");
1312        panic!("{}", message);
1313    }
1314}
1315
1316/// [`FailingStrategy`] that collects the failures from failing assertions.
1317#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1318pub struct CollectFailures;
1319
1320impl FailingStrategy for CollectFailures {
1321    fn do_fail_with(&self, _failures: &[AssertFailure]) {
1322        // do nothing by design
1323    }
1324}
1325
1326/// Used with generic types in expectations where the concrete type is not
1327/// relevant for the failure message.
1328///
1329/// This type implements the std format trait [`std::fmt::Debug`] and
1330/// [`std::fmt::Display`] which both format the value as "_".
1331///
1332/// ```
1333/// # use asserting::prelude::*;
1334/// # use asserting::spec::Unknown;
1335/// assert_that!(format!("{:?}", Unknown)).is_equal_to("_");
1336/// assert_that!(format!("{}", Unknown)).is_equal_to("_");
1337/// ```
1338///
1339/// # Examples
1340///
1341/// This type is used to implement the expectations
1342///
1343/// * [`IsSome`](crate::expectations::IsSome)
1344/// * [`IsNone`](crate::expectations::IsNone)
1345/// * [`IsOk`](crate::expectations::IsOk)
1346/// * [`IsErr`](crate::expectations::IsErr)
1347///
1348/// For example for implementing the function [`Expectation::message()`] for the
1349/// [`IsOk`](crate::expectations::IsOk) expectation for `Result<T, E>` the
1350/// concrete types for `T` and `E` are not relevant. The implementation of the
1351/// trait looks like this:
1352///
1353/// ```no_run
1354/// # use std::fmt::Debug;
1355/// # use asserting::spec::{DiffFormat, Expectation, Expression, Unknown};
1356/// # struct IsOk;
1357/// impl<T, E> Expectation<Result<T, E>> for IsOk
1358/// where
1359///     T: Debug,
1360///     E: Debug,
1361/// {
1362///     fn test(&mut self, subject: &Result<T, E>) -> bool {
1363///         subject.is_ok()
1364///     }
1365///
1366///     fn message(&self, expression: &Expression<'_>, actual: &Result<T, E>, _inverted: bool, _format: &DiffFormat) -> String {
1367///         format!(
1368///             "expected {expression} is {:?}\n   but was: {actual:?}\n  expected: {:?}",
1369///             Ok::<_, Unknown>(Unknown),
1370///             Ok::<_, Unknown>(Unknown),
1371///         )
1372///     }
1373/// }
1374/// ```
1375#[derive(Default, Clone, Copy, PartialEq, Eq)]
1376pub struct Unknown;
1377
1378impl Debug for Unknown {
1379    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1380        write!(f, "{self}")
1381    }
1382}
1383
1384impl Display for Unknown {
1385    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1386        write!(f, "_")
1387    }
1388}
1389
1390/// Wrapper type that holds a closure as code snippet.
1391#[cfg(feature = "panic")]
1392#[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
1393pub struct Code<F>(Rc<RefCell<Option<F>>>);
1394
1395#[cfg(feature = "panic")]
1396mod code {
1397    use super::Code;
1398    use std::cell::RefCell;
1399    use std::rc::Rc;
1400
1401    impl<F> From<F> for Code<F>
1402    where
1403        F: FnOnce(),
1404    {
1405        fn from(value: F) -> Self {
1406            Self(Rc::new(RefCell::new(Some(value))))
1407        }
1408    }
1409
1410    impl<F> Code<F> {
1411        /// Takes the closure out of this `Code` leaving it empty.
1412        #[must_use]
1413        pub fn take(&self) -> Option<F> {
1414            self.0.borrow_mut().take()
1415        }
1416    }
1417}
1418
1419#[cfg(test)]
1420mod tests;