antithesis_sdk/assert/
macros.rs

1#[cfg(feature = "full")]
2#[doc(hidden)]
3#[macro_export]
4macro_rules! function {
5    ($static:ident) => {
6        // Define a do-nothing function `'_f()'` within the context of
7        // the function invoking an assertion.  Then the ``type_name`` of
8        // this do-nothing will be something like:
9        //
10        //     bincrate::binmod::do_stuff::_f
11        //
12        // After trimming off the last three chars ``::_f`` what remains is
13        // the full path to the name of the function invoking the assertion
14        //
15        // The result will be stored as a lazily initialized statics in
16        // `$static`, so that it can be available at
17        // assertion catalog registration time.
18        use $crate::once_cell::sync::Lazy;
19        fn _f() {}
20        static $static: $crate::once_cell::sync::Lazy<&'static str> =
21            $crate::once_cell::sync::Lazy::new(|| {
22                fn type_name_of<T>(_: T) -> &'static str {
23                    ::std::any::type_name::<T>()
24                }
25                let name = type_name_of(_f);
26                &name[..name.len() - 4]
27            });
28    };
29}
30
31/// Common handling used by all the assertion-related macros
32#[cfg(feature = "full")]
33#[doc(hidden)]
34#[macro_export]
35macro_rules! assert_helper {
36    // The handling of this pattern-arm of assert_helper
37    // is wrapped in a block {} to avoid name collisions
38    (condition = $condition:expr, $message:expr, $(details = $details:expr)?, $assert_type:path, $display_type:literal, must_hit = $must_hit:literal) => {{
39        // `$message` must be const evaluable
40        const _: &str = $message;
41
42        // Force evaluation of expressions.
43        let condition = $condition;
44        let details = &$crate::serde_json::json!({});
45        $(let details = $details;)?
46
47        $crate::function!(FUN_NAME);
48
49        use $crate::assert::AssertionCatalogInfo;
50        #[$crate::linkme::distributed_slice($crate::assert::ANTITHESIS_CATALOG)]
51        #[linkme(crate = $crate::linkme)] // Refer to our re-exported linkme.
52        static ALWAYS_CATALOG_ITEM: AssertionCatalogInfo = AssertionCatalogInfo {
53            assert_type: $assert_type,
54            display_type: $display_type,
55            condition: false,
56            message: $message,
57            class: ::std::module_path!(),
58            function: &FUN_NAME, /* function: &Lazy<&str> */
59            file: ::std::file!(),
60            begin_line: ::std::line!(),
61            begin_column: ::std::column!(),
62            must_hit: $must_hit,
63            id: $message,
64        };
65
66        let ptr_function = Lazy::force(&FUN_NAME);
67
68        static TRACKER: $crate::assert::TrackingInfo = $crate::assert::TrackingInfo::new();
69
70        $crate::assert::assert_impl(
71            $assert_type,                     /* assert_type */
72            $display_type,                    /* display_type */
73            condition,                        /* condition */
74            $message,                         /* message */
75            ::std::module_path!(),            /* class */
76            *ptr_function,                    /* function */
77            ::std::file!(),                   /* file */
78            ::std::line!(),                   /* line */
79            ::std::column!(),                 /* column */
80            true,                             /* hit */
81            $must_hit,                        /* must-hit */
82            $message,                         /* id */
83            details,                          /* details */
84            Some(&TRACKER),                   /* tracker */
85        )
86    }}; // end pattern-arm block
87}
88
89#[cfg(not(feature = "full"))]
90#[doc(hidden)]
91#[macro_export]
92macro_rules! assert_helper {
93    (condition = $condition:expr, $message:expr, $(details = $details:expr)?, $assert_type:path, $display_type:literal, must_hit = $must_hit:literal) => {{
94        // `$message` must be const evaluable
95        const _: &str = $message;
96
97        // Force evaluation of expressions, ensuring that
98        // any side effects of these expressions will always be
99        // evaluated at runtime - even if the assertion itself
100        // is supressed by the `no-antithesis-sdk` feature
101        let condition = $condition;
102        $(let details = $details;)?
103    }};
104}
105
106/// Assert that ``condition`` is true every time this function is called, **and** that it is
107/// called at least once. The corresponding test property will be viewable in the ``Antithesis SDK: Always`` group of your triage report.
108///
109/// # Example
110///
111/// ```
112/// use serde_json::{json};
113/// use antithesis_sdk::{assert_always, random};
114///
115/// const MAX_ALLOWED: u64 = 100;
116/// let actual = random::get_random() % 100u64;
117/// let details = json!({"max_allowed": MAX_ALLOWED, "actual": actual});
118/// antithesis_sdk::assert_always!(actual < MAX_ALLOWED, "Value in range", &details);
119/// ```
120///
121/// Ensure that non-const-evaluable messages are rejected.
122///
123/// ```
124/// use serde_json::json;
125/// const MESSAGE: &str = concat!("Value", " in range");
126/// antithesis_sdk::assert_always!(true, MESSAGE, &json!({}));
127/// ```
128///
129/// A message computed at runtime is rejected at compile time:
130///
131/// ```compile_fail
132/// use serde_json::json;
133/// let message = String::from("Value in range");
134/// antithesis_sdk::assert_always!(true, message, &json!({}));
135/// ```
136///
137/// ```compile_fail
138/// use serde_json::json;
139/// antithesis_sdk::assert_always!(true, format!("{}", "Value in range"), &json!({}));
140/// ```
141#[macro_export]
142macro_rules! assert_always {
143    ($condition:expr, $message:expr$(, $details:expr)?) => {
144        $crate::assert_helper!(
145            condition = $condition,
146            $message,
147            $(details = $details)?,
148            $crate::assert::AssertType::Always,
149            "Always",
150            must_hit = true
151        )
152    };
153    ($($rest:tt)*) => {
154        ::std::compile_error!(
155r#"Invalid syntax when calling macro `assert_always`.
156Example usage:
157    `assert_always!(condition_expr, "assertion message (const &'static str)", &details_json_value_expr)`
158"#
159        );
160    };
161}
162
163/// Assert that ``condition`` is true every time this function is called. The corresponding test property will pass even if the assertion is never encountered.
164/// This test property will be viewable in the ``Antithesis SDK: Always`` group of your triage report.
165///
166/// # Example
167///
168/// ```
169/// use serde_json::{json};
170/// use antithesis_sdk::{assert_always_or_unreachable, random};
171///
172/// const MAX_ALLOWED: u64 = 100;
173/// let actual = random::get_random() % 100u64;
174/// let details = json!({"max_allowed": MAX_ALLOWED, "actual": actual});
175/// antithesis_sdk::assert_always_or_unreachable!(actual < MAX_ALLOWED, "Value in range", &details);
176/// ```
177#[macro_export]
178macro_rules! assert_always_or_unreachable {
179    ($condition:expr, $message:expr$(, $details:expr)?) => {
180        $crate::assert_helper!(
181            condition = $condition,
182            $message,
183            $(details = $details)?,
184            $crate::assert::AssertType::Always,
185            "AlwaysOrUnreachable",
186            must_hit = false
187        )
188    };
189    ($($rest:tt)*) => {
190        ::std::compile_error!(
191r#"Invalid syntax when calling macro `assert_always_or_unreachable`.
192Example usage:
193    `assert_always_or_unreachable!(condition_expr, "assertion message (const &'static str)", &details_json_value_expr)`
194"#
195        );
196    };
197}
198
199/// Assert that ``condition`` is true at least one time that this function was called.
200/// (If the assertion is never encountered, the test property will therefore fail.)
201/// This test property will be viewable in the ``Antithesis SDK: Sometimes`` group.
202///
203/// # Example
204///
205/// ```
206/// use serde_json::{json};
207/// use antithesis_sdk::{assert_sometimes, random};
208///
209/// const MAX_ALLOWED: u64 = 100;
210/// let actual = random::get_random() % 120u64;
211/// let details = json!({"max_allowed": MAX_ALLOWED, "actual": actual});
212/// antithesis_sdk::assert_sometimes!(actual > MAX_ALLOWED, "Value in range", &details);
213/// ```
214#[macro_export]
215macro_rules! assert_sometimes {
216    ($condition:expr, $message:expr$(, $details:expr)?) => {
217        $crate::assert_helper!(
218            condition = $condition,
219            $message,
220            $(details = $details)?,
221            $crate::assert::AssertType::Sometimes,
222            "Sometimes",
223            must_hit = true
224        )
225    };
226    ($($rest:tt)*) => {
227        ::std::compile_error!(
228r#"Invalid syntax when calling macro `assert_sometimes`.
229Example usage:
230    `assert_sometimes!(condition_expr, "assertion message (const &'static str)", &details_json_value_expr)`
231"#
232        );
233    };
234}
235
236/// Assert that a line of code is reached at least once.
237/// The corresponding test property will pass if this macro is ever called. (If it is never called the test property will therefore fail.)
238/// This test property will be viewable in the ``Antithesis SDK: Reachablity assertions`` group.
239///
240/// # Example
241///
242/// ```
243/// use serde_json::{json};
244/// use antithesis_sdk::{assert_reachable, random};
245///
246/// const MAX_ALLOWED: u64 = 100;
247/// let actual = random::get_random() % 120u64;
248/// let details = json!({"max_allowed": MAX_ALLOWED, "actual": actual});
249/// if (actual > MAX_ALLOWED) {
250///     antithesis_sdk::assert_reachable!("Value in range", &details);
251/// }
252/// ```
253#[macro_export]
254macro_rules! assert_reachable {
255    ($message:expr$(, $details:expr)?) => {
256        $crate::assert_helper!(
257            condition = true,
258            $message,
259            $(details = $details)?,
260            $crate::assert::AssertType::Reachability,
261            "Reachable",
262            must_hit = true
263        )
264    };
265    ($($rest:tt)*) => {
266        ::std::compile_error!(
267r#"Invalid syntax when calling macro `assert_reachable`.
268Example usage:
269    `assert_reachable!("assertion message (const &'static str)", &details_json_value_expr)`
270"#
271        );
272    };
273}
274
275/// Assert that a line of code is never reached.
276/// The corresponding test property will fail if this macro is ever called.
277/// (If it is never called the test property will therefore pass.)
278/// This test property will be viewable in the ``Antithesis SDK: Reachablity assertions`` group.
279///
280/// # Example
281///
282/// ```
283/// use serde_json::{json};
284/// use antithesis_sdk::{assert_unreachable, random};
285///
286/// const MAX_ALLOWED: u64 = 100;
287/// let actual = random::get_random() % 120u64;
288/// let details = json!({"max_allowed": MAX_ALLOWED, "actual": actual});
289/// if (actual > 120u64) {
290///     antithesis_sdk::assert_unreachable!("Value is above range", &details);
291/// }
292/// ```
293#[macro_export]
294macro_rules! assert_unreachable {
295    ($message:expr$(, $details:expr)?) => {
296        $crate::assert_helper!(
297            condition = false,
298            $message,
299            $(details = $details)?,
300            $crate::assert::AssertType::Reachability,
301            "Unreachable",
302            must_hit = false
303        )
304    };
305    ($($rest:tt)*) => {
306        ::std::compile_error!(
307r#"Invalid syntax when calling macro `assert_unreachable`.
308Example usage:
309    `assert_unreachable!("assertion message (const &'static str)", &details_json_value_expr)`
310"#
311        );
312    };
313}
314
315#[cfg(feature = "full")]
316#[doc(hidden)]
317#[macro_export]
318macro_rules! guidance_helper {
319    ($guidance_type:expr, $message:expr, $maximize:literal, $guidance_data:expr) => {
320        // `$message` must be const evaluable
321        const _: &str = $message;
322
323        $crate::function!(FUN_NAME);
324
325        use $crate::assert::guidance::{GuidanceCatalogInfo, GuidanceType};
326        #[$crate::linkme::distributed_slice($crate::assert::ANTITHESIS_GUIDANCE_CATALOG)]
327        #[linkme(crate = $crate::linkme)] // Refer to our re-exported linkme.
328        static GUIDANCE_CATALOG_ITEM: GuidanceCatalogInfo = GuidanceCatalogInfo {
329            guidance_type: $guidance_type,
330            message: $message,
331            id: $message,
332            class: ::std::module_path!(),
333            function: &FUN_NAME,
334            file: ::std::file!(),
335            begin_line: ::std::line!(),
336            begin_column: ::std::column!(),
337            maximize: $maximize,
338        };
339
340        $crate::assert::guidance::guidance_impl(
341            $guidance_type,
342            $message,
343            $message,
344            ::std::module_path!(),
345            *Lazy::force(&FUN_NAME),
346            ::std::file!(),
347            ::std::line!(),
348            ::std::column!(),
349            $maximize,
350            $guidance_data,
351            true,
352        )
353    };
354}
355
356#[cfg(feature = "full")]
357#[doc(hidden)]
358#[macro_export]
359macro_rules! numeric_guidance_helper {
360    ($assert:path, $op:tt, $maximize:literal, $left:expr, $right:expr, $message:expr$(, $details:expr)?) => {{
361        let left = $left;
362        let right = $right;
363        let details = &$crate::serde_json::json!({});
364        $(let details = $details;)?
365        let mut details = details.clone();
366        details["left"] = left.into();
367        details["right"] = right.into();
368        $assert!(left $op right, $message, &details);
369
370        let guidance_data = $crate::serde_json::json!({
371            "left": left,
372            "right": right,
373        });
374        // TODO: Right now it seems to be impossible for this macro to use the returned
375        // type of `diff` to instanciate the `T` in `Guard<T>`, which has to be
376        // explicitly provided for the static variable `GUARD`.
377        // Instead, we currently fix `T` to be `f64`, and ensure all implementations of `Diff` returns `f64`.
378        // Here are some related language limitations:
379        // - Although `typeof` is a reserved keyword in Rust, it is never implemented. See <https://stackoverflow.com/questions/64890774>.
380        // - Rust does not, and explicitly would not (see https://doc.rust-lang.org/reference/items/static-items.html#statics--generics), support generic static variable.
381        // - Type inference is not performed for static variable, i.e. `Guard<_>` is not allowed.
382        // - Some form of existential type can help, but that's only available in nightly Rust under feature `type_alias_impl_trait`.
383        //
384        // Other approaches I can think of either requires dynamic type tagging that has
385        // runtime overhead, or requires the user of the macro to explicitly provide the type,
386        // which is really not ergonomic and deviate from the APIs from other SDKs.
387        let diff = $crate::assert::guidance::Diff::diff(&left, right);
388        type Guard<T> = $crate::assert::guidance::Guard<$maximize, T>;
389        // TODO: Waiting for [type_alias_impl_trait](https://github.com/rust-lang/rust/issues/63063) to stabilize...
390        // type Distance = impl Minimal;
391        type Distance = f64;
392        static GUARD: Guard<Distance> = Guard::init();
393        if GUARD.should_emit(diff) {
394            $crate::guidance_helper!($crate::assert::guidance::GuidanceType::Numeric, $message, $maximize, guidance_data);
395        }
396    }};
397}
398
399#[cfg(not(feature = "full"))]
400#[doc(hidden)]
401#[macro_export]
402macro_rules! numeric_guidance_helper {
403    ($assert:path, $op:tt, $maximize:literal, $left:expr, $right:expr, $message:expr$(, $details:expr)?) => {{
404        // `$message` must be const evaluable
405        const _: &str = $message;
406        $assert!($left $op $right, $message$(, $details)?);
407    }};
408}
409
410#[cfg(feature = "full")]
411#[doc(hidden)]
412#[macro_export]
413macro_rules! boolean_guidance_helper {
414    ($assert:path, $all:literal, {$($name:ident: $cond:expr),*}, $message:expr$(, $details:expr)?) => {{
415        let details = &$crate::serde_json::json!({});
416        $(let details = $details;)?
417        let mut details = details.clone();
418        let (cond, guidance_data) = {
419            $(let $name = $cond;)*
420            $(details[::std::stringify!($name)] = $name.into();)*
421            (
422                if $all { true $(&& $name)* } else { false $(|| $name)* },
423                $crate::serde_json::json!({$(::std::stringify!($name): $name),*})
424            )
425        };
426        $assert!(cond, $message, &details);
427        $crate::guidance_helper!($crate::assert::guidance::GuidanceType::Boolean, $message, $all, guidance_data);
428    }};
429}
430
431#[cfg(not(feature = "full"))]
432#[doc(hidden)]
433#[macro_export]
434macro_rules! boolean_guidance_helper {
435    ($assert:path, $all:literal, {$($name:ident: $cond:expr),*}, $message:expr$(, $details:expr)?) => {{
436        let cond = {
437            $(let $name = $cond;)*
438            if $all { true $(&& $name)* } else { false $(|| $name)* }
439        };
440        $assert!(cond, $message$(, $details)?);
441    }};
442}
443
444/// `assert_always_greater_than(x, y, ...)` is mostly equivalent to `assert_always!(x > y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
445///
446/// Ensure that non-const-evaluable messages are rejected.
447///
448/// ```
449/// use serde_json::json;
450/// const MESSAGE: &str = concat!("x", " over y");
451/// antithesis_sdk::assert_always_greater_than!(2, 1, MESSAGE, &json!({}));
452/// ```
453///
454/// ```compile_fail
455/// use serde_json::json;
456/// antithesis_sdk::assert_always_greater_than!(2, 1, format!("{}", "x over y"), &json!({}));
457/// ```
458#[macro_export]
459macro_rules! assert_always_greater_than {
460    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
461        $crate::numeric_guidance_helper!($crate::assert_always, >, false, $left, $right, $message$(, $details)?)
462    };
463    ($($rest:tt)*) => {
464        ::std::compile_error!(
465r#"Invalid syntax when calling macro `assert_always_greater_than`.
466Example usage:
467    `assert_always_greater_than!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
468"#
469        );
470    };
471}
472
473/// `assert_always_greater_than_or_equal_to(x, y, ...)` is mostly equivalent to `assert_always!(x >= y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
474#[macro_export]
475macro_rules! assert_always_greater_than_or_equal_to {
476    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
477        $crate::numeric_guidance_helper!($crate::assert_always, >=, false, $left, $right, $message$(, $details)?)
478    };
479    ($($rest:tt)*) => {
480        ::std::compile_error!(
481r#"Invalid syntax when calling macro `assert_always_greater_than_or_equal_to`.
482Example usage:
483    `assert_always_greater_than_or_equal_to!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
484"#
485        );
486    };
487}
488
489/// `assert_always_less_than(x, y, ...)` is mostly equivalent to `assert_always!(x < y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
490#[macro_export]
491macro_rules! assert_always_less_than {
492    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
493        $crate::numeric_guidance_helper!($crate::assert_always, <, true, $left, $right, $message$(, $details)?)
494    };
495    ($($rest:tt)*) => {
496        ::std::compile_error!(
497r#"Invalid syntax when calling macro `assert_always_less_than`.
498Example usage:
499    `assert_always_less_than!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
500"#
501        );
502    };
503}
504
505/// `assert_always_less_than_or_equal_to(x, y, ...)` is mostly equivalent to `assert_always!(x <= y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
506#[macro_export]
507macro_rules! assert_always_less_than_or_equal_to {
508    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
509        $crate::numeric_guidance_helper!($crate::assert_always, <=, true, $left, $right, $message$(, $details)?)
510    };
511    ($($rest:tt)*) => {
512        ::std::compile_error!(
513r#"Invalid syntax when calling macro `assert_always_less_than_or_equal_to`.
514Example usage:
515    `assert_always_less_than_or_equal_to!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
516"#
517        );
518    };
519}
520
521/// `assert_sometimes_greater_than(x, y, ...)` is mostly equivalent to `assert_sometimes!(x > y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
522#[macro_export]
523macro_rules! assert_sometimes_greater_than {
524    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
525        $crate::numeric_guidance_helper!($crate::assert_sometimes, >, true, $left, $right, $message$(, $details)?)
526    };
527    ($($rest:tt)*) => {
528        ::std::compile_error!(
529r#"Invalid syntax when calling macro `assert_sometimes_greater_than`.
530Example usage:
531    `assert_sometimes_greater_than!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
532"#
533        );
534    };
535}
536
537/// `assert_sometimes_greater_than_or_equal_to(x, y, ...)` is mostly equivalent to `assert_sometimes!(x >= y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
538#[macro_export]
539macro_rules! assert_sometimes_greater_than_or_equal_to {
540    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
541        $crate::numeric_guidance_helper!($crate::assert_sometimes, >=, true, $left, $right, $message$(, $details)?)
542    };
543    ($($rest:tt)*) => {
544        ::std::compile_error!(
545r#"Invalid syntax when calling macro `assert_sometimes_greater_than_or_equal_to`.
546Example usage:
547    `assert_sometimes_greater_than_or_equal_to!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
548"#
549        );
550    };
551}
552
553/// `assert_sometimes_less_than(x, y, ...)` is mostly equivalent to `assert_sometimes!(x < y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
554#[macro_export]
555macro_rules! assert_sometimes_less_than {
556    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
557        $crate::numeric_guidance_helper!($crate::assert_sometimes, <, false, $left, $right, $message$(, $details)?)
558    };
559    ($($rest:tt)*) => {
560        ::std::compile_error!(
561r#"Invalid syntax when calling macro `assert_sometimes_less_than`.
562Example usage:
563    `assert_sometimes_less_than!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
564"#
565        );
566    };
567}
568
569/// `assert_sometimes_less_than_or_equal_to(x, y, ...)` is mostly equivalent to `assert_sometimes!(x <= y, ...)`, except Antithesis has more visibility to the value of `x` and `y`, and the assertion details would be merged with `{"left": x, "right": y}`.
570#[macro_export]
571macro_rules! assert_sometimes_less_than_or_equal_to {
572    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
573        $crate::numeric_guidance_helper!($crate::assert_sometimes, <=, false, $left, $right, $message$(, $details)?)
574    };
575    ($($rest:tt)*) => {
576        ::std::compile_error!(
577r#"Invalid syntax when calling macro `assert_sometimes_less_than_or_equal_to`.
578Example usage:
579    `assert_sometimes_less_than_or_equal_to!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
580"#
581        );
582    };
583}
584
585/// `assert_always_some({a: x, b: y, ...})` is similar to `assert_always(x || y || ...)`, except:
586/// - Antithesis has more visibility to the individual propositions.
587/// - There is no short-circuiting, so all of `x`, `y`, ... would be evaluated.
588/// - The assertion details would be merged with `{"a": x, "b": y, ...}`.
589///
590/// Ensure that non-const-evaluable messages are rejected.
591///
592/// ```
593/// use serde_json::json;
594/// const MESSAGE: &str = concat!("at least ", "one");
595/// antithesis_sdk::assert_always_some!({a: true, b: false}, MESSAGE, &json!({}));
596/// ```
597///
598/// ```compile_fail
599/// use serde_json::json;
600/// antithesis_sdk::assert_always_some!({a: true, b: false}, format!("{}", "at least one"), &json!({}));
601/// ```
602#[macro_export]
603macro_rules! assert_always_some {
604    ({$($($name:ident: $cond:expr),+ $(,)?)?}, $message:expr$(, $details:expr)?) => {
605        $crate::boolean_guidance_helper!($crate::assert_always, false, {$($($name: $cond),+)?}, $message$(, $details)?);
606    };
607    ($($rest:tt)*) => {
608        ::std::compile_error!(
609r#"Invalid syntax when calling macro `assert_always_some`.
610Example usage:
611    `assert_always_some!({field1: cond1, field2: cond2, ...}, "assertion message (const &'static str)", &details_json_value_expr)`
612"#
613        );
614    };
615}
616
617/// `assert_sometimes_all({a: x, b: y, ...})` is similar to `assert_sometimes(x && y && ...)`, except:
618/// - Antithesis has more visibility to the individual propositions.
619/// - There is no short-circuiting, so all of `x`, `y`, ... would be evaluated.
620/// - The assertion details would be merged with `{"a": x, "b": y, ...}`.
621#[macro_export]
622macro_rules! assert_sometimes_all {
623    ({$($($name:ident: $cond:expr),+ $(,)?)?}, $message:expr$(, $details:expr)?) => {
624        $crate::boolean_guidance_helper!($crate::assert_sometimes, true, {$($($name: $cond),+)?}, $message$(, $details)?);
625    };
626    ($($rest:tt)*) => {
627        ::std::compile_error!(
628r#"Invalid syntax when calling macro `assert_sometimes_all`.
629Example usage:
630    `assert_sometimes_all!({field1: cond1, field2: cond2, ...}, "assertion message (const &'static str)", &details_json_value_expr)`
631"#
632        );
633    };
634}