Skip to main content

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 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 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 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 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 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 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 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 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: 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) -> &Expression<'_> {
649        &self.expression
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 fn new(subject: S, failing_strategy: R) -> Self {
696        Self {
697            subject,
698            expression: Expression::default(),
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 = 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 used to highlight differences between the actual
730    /// 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 to the configured mode.
743    ///
744    /// The mode is configured via environment variables as 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 a 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
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, the assertion fails according to
866    /// the current 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 message =
887                expectation.message(&self.expression, &self.subject, false, &self.diff_format);
888            self.do_fail_with_message(message);
889        }
890        self
891    }
892
893    /// Asserts whether the given predicate is meet.
894    ///
895    /// This method takes a predicate function and calls it as an expectation.
896    /// In case the predicate function returns false, it does fail with a
897    /// generic failure message and according to the current failing strategy of
898    /// this `Spec`.
899    ///
900    /// This method can be used to do simple custom assertions without
901    /// implementing an [`Expectation`] and an assertion trait.
902    ///
903    /// # Examples
904    ///
905    /// ```
906    /// use asserting::prelude::*;
907    ///
908    /// fn is_odd(value: &i32) -> bool {
909    ///     value & 1 == 1
910    /// }
911    ///
912    /// assert_that!(37).satisfies(is_odd);
913    ///
914    /// let failures = verify_that!(22).satisfies(is_odd).display_failures();
915    ///
916    /// assert_that!(failures).contains_exactly([
917    ///     "expected 22 to satisfy the given predicate, but returned false\n"
918    /// ]);
919    /// ```
920    ///
921    /// To assert a predicate with a custom failure message instead of the
922    /// generic one, use the method
923    /// [`satisfies_with_message`](Spec::satisfies_with_message).
924    #[allow(clippy::return_self_not_must_use)]
925    #[track_caller]
926    pub fn satisfies<P>(self, predicate: P) -> Self
927    where
928        P: Fn(&S) -> bool,
929    {
930        self.expecting(satisfies(predicate))
931    }
932
933    /// Asserts whether the given predicate is meet.
934    ///
935    /// This method takes a predicate function and calls it as an expectation.
936    /// In case the predicate function returns false, it does fail with the
937    /// provided failure message and according to the current failing strategy
938    /// of this `Spec`.
939    ///
940    /// This method can be used to do simple custom assertions without
941    /// implementing an [`Expectation`] and an assertion trait.
942    ///
943    /// # Examples
944    ///
945    /// ```
946    /// use asserting::prelude::*;
947    ///
948    /// fn is_odd(value: &i32) -> bool {
949    ///     value & 1 == 1
950    /// }
951    ///
952    /// assert_that!(37).satisfies_with_message("expected my number to be odd", is_odd);
953    ///
954    /// let failures = verify_that!(22)
955    ///         .satisfies_with_message("expected my number to be odd", is_odd)
956    ///         .display_failures();
957    ///
958    /// assert_that!(failures).contains_exactly([
959    ///     "expected my number to be odd\n"
960    /// ]);
961    /// ```
962    ///
963    /// To assert a predicate with a generic failure message instead of
964    /// providing one use the method
965    /// [`satisfies`](Spec::satisfies).
966    #[allow(clippy::return_self_not_must_use)]
967    #[track_caller]
968    pub fn satisfies_with_message<P>(self, message: impl Into<String>, predicate: P) -> Self
969    where
970        P: Fn(&S) -> bool,
971    {
972        self.expecting(satisfies(predicate).with_message(message))
973    }
974
975    /// Fails the assertion according to the current failing strategy of this
976    /// `Spec`.
977    #[track_caller]
978    pub fn do_fail_with_message(&mut self, message: impl Into<String>) {
979        let message = message.into();
980        let failure = AssertFailure {
981            description: self.description.clone().map(String::from),
982            message,
983            location: self.location.map(OwnedLocation::from),
984        };
985        self.failures.push(failure);
986        self.failing_strategy.do_fail_with(&self.failures);
987    }
988}
989
990impl<S> Spec<'_, S, CollectFailures> {
991    /// Turns assertions into "soft assertions".
992    ///
993    /// It executes all specified assertions on a `Spec` and if at least one
994    /// assertion fails, it panics. The panic message contains the messages of
995    /// all assertions that have failed.
996    ///
997    /// This method is only available on `Spec`s with the
998    /// [`CollectFailures`]-[`FailingStrategy`]. That is any `Spec` contructed
999    /// by the macros [`verify_that!`] and [`verify_that_code!`] or by the
1000    /// functions [`verify_that()`] and [`verify_that_code()`].
1001    ///
1002    /// On a `Spec` with the [`PanicOnFail`]-[`FailingStrategy`] it would not
1003    /// work as the very first failing assertion panics immediately, and later
1004    /// assertions never get executed.
1005    ///
1006    /// # Examples
1007    ///
1008    /// Running the following two assertions in "soft" mode:
1009    ///
1010    /// ```should_panic
1011    /// use asserting::prelude::*;
1012    ///
1013    /// verify_that!("the answer to all important questions is 42")
1014    ///     .contains("unimportant")
1015    ///     .has_at_most_length(41)
1016    ///     .soft_panic();
1017    /// ```
1018    ///
1019    /// executes both assertions and prints the messages of both failing
1020    /// assertions in the panic message:
1021    ///
1022    /// ```console
1023    /// expected subject to contain "unimportant"
1024    ///    but was: "the answer to all important questions is 42"
1025    ///   expected: "unimportant"
1026    ///
1027    /// expected subject to have at most a length of 41
1028    ///    but was: 43
1029    ///   expected: <= 41
1030    /// ```
1031    ///
1032    /// To highlight differences in failure messages of soft assertions use
1033    /// the `with_configured_diff_format()` method, like so:
1034    ///
1035    /// ```
1036    /// # #[cfg(not(feature = "colored"))]
1037    /// # fn main() {}
1038    /// # #[cfg(feature = "colored")]
1039    /// # fn main() {
1040    /// use asserting::prelude::*;
1041    ///
1042    /// verify_that!("the answer to all important questions is 42")
1043    ///     .with_configured_diff_format()
1044    ///     .contains("important")
1045    ///     .has_at_most_length(43)
1046    ///     .soft_panic();
1047    /// # }
1048    /// ```
1049    pub fn soft_panic(&self) {
1050        if !self.failures.is_empty() {
1051            PanicOnFail.do_fail_with(&self.failures);
1052        }
1053    }
1054}
1055
1056impl<'a, I, R> Spec<'a, I, R> {
1057    /// Iterates over the elements of a collection or an iterator and executes
1058    /// the given assertions for each of those elements. If all elements are
1059    /// asserted successfully, the whole assertion succeeds.
1060    ///
1061    /// It iterates over all elements of the collection or iterator and collects
1062    /// the failure messages for those elements where the assertion fails. In
1063    /// other words, it does not stop iterating when the assertion for one
1064    /// element fails.
1065    ///
1066    /// The failure messages contain the position of the element within the
1067    /// collection or iterator. The position is 0-based. So a failure message
1068    /// for the first element contains `[0]`, the second `[1]`, and so on.
1069    ///
1070    /// # Example
1071    ///
1072    /// The following assertion:
1073    ///
1074    /// ```should_panic
1075    /// use asserting::prelude::*;
1076    ///
1077    /// let numbers = [2, 4, 6, 8, 10];
1078    ///
1079    /// assert_that!(numbers).each_element(|e|
1080    ///     e.is_greater_than(2)
1081    ///         .is_at_most(7)
1082    /// );
1083    /// ```
1084    ///
1085    /// will print:
1086    ///
1087    /// ```console
1088    /// expected numbers [0] to be greater than 2
1089    ///    but was: 2
1090    ///   expected: > 2
1091    ///
1092    /// expected numbers [3] to be at most 7
1093    ///    but was: 8
1094    ///   expected: <= 7
1095    ///
1096    /// expected numbers [4] to be at most 7
1097    ///    but was: 10
1098    ///   expected: <= 7
1099    /// ```
1100    #[allow(clippy::return_self_not_must_use)]
1101    #[track_caller]
1102    pub fn each_element<T, A, B>(mut self, assert: A) -> Spec<'a, (), R>
1103    where
1104        I: IntoIterator<Item = T>,
1105        A: Fn(Spec<'a, T, CollectFailures>) -> Spec<'a, B, CollectFailures>,
1106    {
1107        let root_expression = &self.expression;
1108        let mut position = -1;
1109        for item in self.subject {
1110            position += 1;
1111            let element_spec = Spec {
1112                subject: item,
1113                expression: format!("{root_expression} [{position}]").into(),
1114                description: None,
1115                location: self.location,
1116                failures: vec![],
1117                diff_format: self.diff_format.clone(),
1118                failing_strategy: CollectFailures,
1119            };
1120            let failures = assert(element_spec).failures;
1121            self.failures.extend(failures);
1122        }
1123        if !self.failures.is_empty()
1124            && any::type_name_of_val(&self.failing_strategy) == any::type_name::<PanicOnFail>()
1125        {
1126            PanicOnFail.do_fail_with(&self.failures);
1127        }
1128        Spec {
1129            subject: (),
1130            expression: self.expression,
1131            description: self.description,
1132            location: self.location,
1133            failures: self.failures,
1134            diff_format: self.diff_format,
1135            failing_strategy: self.failing_strategy,
1136        }
1137    }
1138
1139    /// Iterates over the elements of a collection or an iterator and executes
1140    /// the given assertions for each of those elements. If the assertion of any
1141    /// element is successful, the iteration stops and the whole assertion
1142    /// succeeds.
1143    ///
1144    /// If the assertion fails for all elements, the failures of the assertion
1145    /// for all elements are collected.
1146    ///
1147    /// The failure messages contain the position of the element within the
1148    /// collection or iterator. The position is 0-based. So a failure message
1149    /// for the first element contains `[0]`, the second `[1]`, and so on.
1150    ///
1151    /// # Example
1152    ///
1153    /// The following assertion:
1154    ///
1155    /// ```should_panic
1156    /// use asserting::prelude::*;
1157    ///
1158    /// let digit_names = ["one", "two", "three"];
1159    ///
1160    /// assert_that!(digit_names).any_element(|e|
1161    ///     e.contains('x')
1162    /// );
1163    /// ```
1164    ///
1165    /// will print:
1166    ///
1167    /// ```console
1168    /// expected digit_names [0] to contain 'x'
1169    ///    but was: "one"
1170    ///   expected: 'x'
1171    ///
1172    /// expected digit_names [1] to contain 'x'
1173    ///    but was: "two"
1174    ///   expected: 'x'
1175    ///
1176    /// expected digit_names [2] to contain 'x'
1177    ///    but was: "three"
1178    ///   expected: 'x'
1179    /// ```
1180    #[track_caller]
1181    pub fn any_element<T, A, B>(mut self, assert: A) -> Spec<'a, (), R>
1182    where
1183        I: IntoIterator<Item = T>,
1184        A: Fn(Spec<'a, T, CollectFailures>) -> Spec<'a, B, CollectFailures>,
1185    {
1186        let root_expression = &self.expression;
1187        let mut any_success = false;
1188        let mut position = -1;
1189        for item in self.subject {
1190            position += 1;
1191            let element_spec = Spec {
1192                subject: item,
1193                expression: format!("{root_expression} [{position}]").into(),
1194                description: None,
1195                location: self.location,
1196                failures: vec![],
1197                diff_format: self.diff_format.clone(),
1198                failing_strategy: CollectFailures,
1199            };
1200            let failures = assert(element_spec).failures;
1201            if failures.is_empty() {
1202                any_success = true;
1203                break;
1204            }
1205            self.failures.extend(failures);
1206        }
1207        if !any_success
1208            && any::type_name_of_val(&self.failing_strategy) == any::type_name::<PanicOnFail>()
1209        {
1210            PanicOnFail.do_fail_with(&self.failures);
1211        }
1212        Spec {
1213            subject: (),
1214            expression: self.expression,
1215            description: self.description,
1216            location: self.location,
1217            failures: self.failures,
1218            diff_format: self.diff_format,
1219            failing_strategy: self.failing_strategy,
1220        }
1221    }
1222}
1223
1224/// An error describing a failed assertion.
1225///
1226/// This struct implements the [`std::error::Error`] trait.
1227#[derive(Debug, Clone, PartialEq, Eq)]
1228pub struct AssertFailure {
1229    description: Option<String>,
1230    message: String,
1231    location: Option<OwnedLocation>,
1232}
1233
1234impl Display for AssertFailure {
1235    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1236        match &self.description {
1237            None => {
1238                writeln!(f, "{}", &self.message)?;
1239            },
1240            Some(description) => {
1241                writeln!(f, "{description}\n{}", &self.message)?;
1242            },
1243        }
1244        Ok(())
1245    }
1246}
1247
1248impl StdError for AssertFailure {}
1249
1250#[allow(clippy::must_use_candidate)]
1251impl AssertFailure {
1252    /// Returns the description of the assertion that failed.
1253    pub fn description(&self) -> Option<&String> {
1254        self.description.as_ref()
1255    }
1256
1257    /// Returns the failure message of the assertion that failed.
1258    #[allow(clippy::missing_const_for_fn)]
1259    pub fn message(&self) -> &str {
1260        &self.message
1261    }
1262
1263    /// Returns the location of the assertion in the source code / test code if
1264    /// it has been set in the [`Spec`].
1265    pub fn location(&self) -> Option<&OwnedLocation> {
1266        self.location.as_ref()
1267    }
1268}
1269
1270/// Start and end tag that marks a highlighted part of a string.
1271#[derive(Debug, Clone, PartialEq, Eq)]
1272pub(crate) struct Highlight {
1273    pub(crate) start: &'static str,
1274    pub(crate) end: &'static str,
1275}
1276
1277/// Definition of format properties for highlighting differences between two
1278/// values.
1279#[derive(Debug, Clone, PartialEq, Eq)]
1280pub struct DiffFormat {
1281    pub(crate) unexpected: Highlight,
1282    pub(crate) missing: Highlight,
1283}
1284
1285/// Defines the behavior when an assertion fails.
1286///
1287/// This crate provides two implementations:
1288///
1289/// * [`PanicOnFail`] - panics when an assertion fails
1290/// * [`CollectFailures`] - collects [`AssertFailure`]s of assertions that have failed.
1291pub trait FailingStrategy {
1292    /// Reacts to an assertion that has failed with the [`AssertFailure`]s given
1293    /// as argument.
1294    fn do_fail_with(&self, failures: &[AssertFailure]);
1295}
1296
1297/// [`FailingStrategy`] that panics when an assertion fails.
1298#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1299pub struct PanicOnFail;
1300
1301impl FailingStrategy for PanicOnFail {
1302    #[track_caller]
1303    fn do_fail_with(&self, failures: &[AssertFailure]) {
1304        let message = failures
1305            .iter()
1306            .map(ToString::to_string)
1307            .collect::<Vec<_>>()
1308            .join("\n");
1309        panic!("{}", message);
1310    }
1311}
1312
1313/// [`FailingStrategy`] that collects the failures from failing assertions.
1314#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
1315pub struct CollectFailures;
1316
1317impl FailingStrategy for CollectFailures {
1318    fn do_fail_with(&self, _failures: &[AssertFailure]) {
1319        // do nothing by design
1320    }
1321}
1322
1323/// Used with generic types in expectations where the concrete type is not
1324/// relevant for the failure message.
1325///
1326/// This type implements the std format trait [`std::fmt::Debug`] and
1327/// [`std::fmt::Display`] which both format the value as "_".
1328///
1329/// ```
1330/// # use asserting::prelude::*;
1331/// # use asserting::spec::Unknown;
1332/// assert_that!(format!("{:?}", Unknown)).is_equal_to("_");
1333/// assert_that!(format!("{}", Unknown)).is_equal_to("_");
1334/// ```
1335///
1336/// # Examples
1337///
1338/// This type is used to implement the expectations
1339///
1340/// * [`IsSome`](crate::expectations::IsSome)
1341/// * [`IsNone`](crate::expectations::IsNone)
1342/// * [`IsOk`](crate::expectations::IsOk)
1343/// * [`IsErr`](crate::expectations::IsErr)
1344///
1345/// For example, for implementing the function [`Expectation::message()`] for
1346/// the [`IsOk`](crate::expectations::IsOk) expectation for `Result<T, E>` the
1347/// concrete types for `T` and `E` are not relevant. The implementation of the
1348/// trait looks like this:
1349///
1350/// ```no_run
1351/// # use std::fmt::Debug;
1352/// # use asserting::spec::{DiffFormat, Expectation, Expression, Unknown};
1353/// # struct IsOk;
1354/// impl<T, E> Expectation<Result<T, E>> for IsOk
1355/// where
1356///     T: Debug,
1357///     E: Debug,
1358/// {
1359///     fn test(&mut self, subject: &Result<T, E>) -> bool {
1360///         subject.is_ok()
1361///     }
1362///
1363///     fn message(&self, expression: &Expression<'_>, actual: &Result<T, E>, _inverted: bool, _format: &DiffFormat) -> String {
1364///         format!(
1365///             "expected {expression} is {:?}\n   but was: {actual:?}\n  expected: {:?}",
1366///             Ok::<_, Unknown>(Unknown),
1367///             Ok::<_, Unknown>(Unknown),
1368///         )
1369///     }
1370/// }
1371/// ```
1372#[derive(Default, Clone, Copy, PartialEq, Eq)]
1373pub struct Unknown;
1374
1375impl Debug for Unknown {
1376    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1377        write!(f, "{self}")
1378    }
1379}
1380
1381impl Display for Unknown {
1382    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1383        write!(f, "_")
1384    }
1385}
1386
1387/// Wrapper type that holds a closure as code snippet.
1388#[cfg(feature = "panic")]
1389#[cfg_attr(docsrs, doc(cfg(feature = "panic")))]
1390pub struct Code<F>(Rc<RefCell<Option<F>>>);
1391
1392#[cfg(feature = "panic")]
1393mod code {
1394    use super::Code;
1395    use std::cell::RefCell;
1396    use std::rc::Rc;
1397
1398    impl<F> From<F> for Code<F>
1399    where
1400        F: FnOnce(),
1401    {
1402        fn from(value: F) -> Self {
1403            Self(Rc::new(RefCell::new(Some(value))))
1404        }
1405    }
1406
1407    impl<F> Code<F> {
1408        /// Takes the closure out of this `Code` leaving it empty.
1409        #[must_use]
1410        pub fn take(&self) -> Option<F> {
1411            self.0.borrow_mut().take()
1412        }
1413    }
1414}
1415
1416#[cfg(test)]
1417mod tests;