Skip to main content

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::Value::Null;
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,
107/// **and** that it is called at least once. The corresponding test property
108/// will be viewable in the ``Antithesis SDK: Always`` group of your triage
109/// report.
110///
111/// # Example
112///
113/// ```
114/// use serde::Serialize;
115/// use antithesis_sdk::{assert_always, random};
116///
117/// // Details can be any Serialize type; non-null scalars and arrays are
118/// // wrapped under "value".
119/// // A borrowed struct avoids serialization on evaluations that are not emitted.
120/// #[derive(Serialize)]
121/// struct Details<'a> {
122///     max_allowed: u64,
123///     actual: u64,
124///     source: &'a str,
125/// }
126///
127/// const MAX_ALLOWED: u64 = 100;
128/// let actual = random::get_random() % 100u64;
129/// let details = Details { max_allowed: MAX_ALLOWED, actual, source: "demo" };
130/// antithesis_sdk::assert_always!(actual < MAX_ALLOWED, "Value in range", &details);
131/// ```
132///
133/// Ensure that non-const-evaluable messages are rejected.
134///
135/// ```
136/// use serde_json::json;
137/// const MESSAGE: &str = concat!("Value", " in range");
138/// antithesis_sdk::assert_always!(true, MESSAGE, &json!({}));
139/// ```
140///
141/// A message computed at runtime is rejected at compile time:
142///
143/// ```compile_fail
144/// use serde_json::json;
145/// let message = String::from("Value in range");
146/// antithesis_sdk::assert_always!(true, message, &json!({}));
147/// ```
148///
149/// ```compile_fail
150/// use serde_json::json;
151/// antithesis_sdk::assert_always!(true, format!("{}", "Value in range"), &json!({}));
152/// ```
153#[macro_export]
154macro_rules! assert_always {
155    ($condition:expr, $message:expr$(, $details:expr)?) => {
156        $crate::assert_helper!(
157            condition = $condition,
158            $message,
159            $(details = $details)?,
160            $crate::assert::AssertType::Always,
161            "Always",
162            must_hit = true
163        )
164    };
165    ($($rest:tt)*) => {
166        ::std::compile_error!(
167r#"Invalid syntax when calling macro `assert_always`.
168Example usage:
169    `assert_always!(condition_expr, "assertion message (const &'static str)", &details_json_value_expr)`
170"#
171        );
172    };
173}
174
175/// Assert that ``condition`` is true every time this function is called. The
176/// corresponding test property will pass even if the assertion is never
177/// encountered. This test property will be viewable in the
178/// ``Antithesis SDK: Always`` group of your triage report.
179///
180/// # Example
181///
182/// ```
183/// use serde::Serialize;
184/// use antithesis_sdk::{assert_always_or_unreachable, random};
185///
186/// // Only serialized when the assertion emits; see assert_always.
187/// #[derive(Serialize)]
188/// struct Details { max_allowed: u64, actual: u64 }
189///
190/// const MAX_ALLOWED: u64 = 100;
191/// let actual = random::get_random() % 100u64;
192/// let details = Details { max_allowed: MAX_ALLOWED, actual };
193/// antithesis_sdk::assert_always_or_unreachable!(actual < MAX_ALLOWED, "Value in range", &details);
194/// ```
195#[macro_export]
196macro_rules! assert_always_or_unreachable {
197    ($condition:expr, $message:expr$(, $details:expr)?) => {
198        $crate::assert_helper!(
199            condition = $condition,
200            $message,
201            $(details = $details)?,
202            $crate::assert::AssertType::Always,
203            "AlwaysOrUnreachable",
204            must_hit = false
205        )
206    };
207    ($($rest:tt)*) => {
208        ::std::compile_error!(
209r#"Invalid syntax when calling macro `assert_always_or_unreachable`.
210Example usage:
211    `assert_always_or_unreachable!(condition_expr, "assertion message (const &'static str)", &details_json_value_expr)`
212"#
213        );
214    };
215}
216
217/// Assert that ``condition`` is true at least one time that this function was
218/// called. (If the assertion is never encountered, the test property will
219/// therefore fail.) This test property will be viewable in the
220/// ``Antithesis SDK: Sometimes`` group.
221///
222/// # Example
223///
224/// ```
225/// use serde::Serialize;
226/// use antithesis_sdk::{assert_sometimes, random};
227///
228/// // Only serialized when the assertion emits; see assert_always.
229/// #[derive(Serialize)]
230/// struct Details { max_allowed: u64, actual: u64 }
231///
232/// const MAX_ALLOWED: u64 = 100;
233/// let actual = random::get_random() % 120u64;
234/// let details = Details { max_allowed: MAX_ALLOWED, actual };
235/// antithesis_sdk::assert_sometimes!(actual > MAX_ALLOWED, "Value in range", &details);
236/// ```
237#[macro_export]
238macro_rules! assert_sometimes {
239    ($condition:expr, $message:expr$(, $details:expr)?) => {
240        $crate::assert_helper!(
241            condition = $condition,
242            $message,
243            $(details = $details)?,
244            $crate::assert::AssertType::Sometimes,
245            "Sometimes",
246            must_hit = true
247        )
248    };
249    ($($rest:tt)*) => {
250        ::std::compile_error!(
251r#"Invalid syntax when calling macro `assert_sometimes`.
252Example usage:
253    `assert_sometimes!(condition_expr, "assertion message (const &'static str)", &details_json_value_expr)`
254"#
255        );
256    };
257}
258
259/// Assert that a line of code is reached at least once. The corresponding test
260/// property will pass if this macro is ever called. (If it is never called the
261/// test property will therefore fail.) This test property will be viewable in
262/// the ``Antithesis SDK: Reachablity assertions`` group.
263///
264/// # Example
265///
266/// ```
267/// use serde::Serialize;
268/// use antithesis_sdk::{assert_reachable, random};
269///
270/// // Only serialized when the assertion emits; see assert_always.
271/// #[derive(Serialize)]
272/// struct Details { max_allowed: u64, actual: u64 }
273///
274/// const MAX_ALLOWED: u64 = 100;
275/// let actual = random::get_random() % 120u64;
276/// let details = Details { max_allowed: MAX_ALLOWED, actual };
277/// if (actual > MAX_ALLOWED) {
278///     antithesis_sdk::assert_reachable!("Value in range", &details);
279/// }
280/// ```
281#[macro_export]
282macro_rules! assert_reachable {
283    ($message:expr$(, $details:expr)?) => {
284        $crate::assert_helper!(
285            condition = true,
286            $message,
287            $(details = $details)?,
288            $crate::assert::AssertType::Reachability,
289            "Reachable",
290            must_hit = true
291        )
292    };
293    ($($rest:tt)*) => {
294        ::std::compile_error!(
295r#"Invalid syntax when calling macro `assert_reachable`.
296Example usage:
297    `assert_reachable!("assertion message (const &'static str)", &details_json_value_expr)`
298"#
299        );
300    };
301}
302
303/// Assert that a line of code is never reached. The corresponding test property
304/// will fail if this macro is ever called. (If it is never called the test
305/// property will therefore pass.) This test property will be viewable in the
306/// ``Antithesis SDK: Reachablity assertions`` group.
307///
308/// # Example
309///
310/// ```
311/// use serde::Serialize;
312/// use antithesis_sdk::{assert_unreachable, random};
313///
314/// // Only serialized when the assertion emits; see assert_always.
315/// #[derive(Serialize)]
316/// struct Details { max_allowed: u64, actual: u64 }
317///
318/// const MAX_ALLOWED: u64 = 100;
319/// let actual = random::get_random() % 120u64;
320/// let details = Details { max_allowed: MAX_ALLOWED, actual };
321/// if (actual > 120u64) {
322///     antithesis_sdk::assert_unreachable!("Value is above range", &details);
323/// }
324/// ```
325#[macro_export]
326macro_rules! assert_unreachable {
327    ($message:expr$(, $details:expr)?) => {
328        $crate::assert_helper!(
329            condition = false,
330            $message,
331            $(details = $details)?,
332            $crate::assert::AssertType::Reachability,
333            "Unreachable",
334            must_hit = false
335        )
336    };
337    ($($rest:tt)*) => {
338        ::std::compile_error!(
339r#"Invalid syntax when calling macro `assert_unreachable`.
340Example usage:
341    `assert_unreachable!("assertion message (const &'static str)", &details_json_value_expr)`
342"#
343        );
344    };
345}
346
347#[cfg(feature = "full")]
348#[doc(hidden)]
349#[macro_export]
350macro_rules! guidance_helper {
351    ($guidance_type:expr, $message:expr, $maximize:literal, $guidance_data:expr) => {
352        // `$message` must be const evaluable
353        const _: &str = $message;
354
355        $crate::function!(FUN_NAME);
356
357        use $crate::assert::guidance::{GuidanceCatalogInfo, GuidanceType};
358        #[$crate::linkme::distributed_slice($crate::assert::ANTITHESIS_GUIDANCE_CATALOG)]
359        #[linkme(crate = $crate::linkme)] // Refer to our re-exported linkme.
360        static GUIDANCE_CATALOG_ITEM: GuidanceCatalogInfo = GuidanceCatalogInfo {
361            guidance_type: $guidance_type,
362            message: $message,
363            id: $message,
364            class: ::std::module_path!(),
365            function: &FUN_NAME,
366            file: ::std::file!(),
367            begin_line: ::std::line!(),
368            begin_column: ::std::column!(),
369            maximize: $maximize,
370        };
371
372        $crate::assert::guidance::guidance_impl(
373            $guidance_type,
374            $message,
375            $message,
376            ::std::module_path!(),
377            *Lazy::force(&FUN_NAME),
378            ::std::file!(),
379            ::std::line!(),
380            ::std::column!(),
381            $maximize,
382            $guidance_data,
383            true,
384        )
385    };
386}
387
388#[cfg(feature = "full")]
389#[doc(hidden)]
390#[macro_export]
391macro_rules! numeric_guidance_helper {
392    ($assert:path, $op:tt, $maximize:literal, $left:expr, $right:expr, $message:expr$(, $details:expr)?) => {{
393        let left = $left;
394        let right = $right;
395        let details = &$crate::serde_json::Value::Null;
396        $(let details = $details;)?
397        let details = $crate::details::WithGuidance {
398            details,
399            guidance: || $crate::serde_json::json!({ "left": left, "right": right }),
400            context: $message,
401        };
402        $assert!(left $op right, $message, &details);
403
404        // TODO: Right now it seems to be impossible for this macro to use the returned
405        // type of `diff` to instanciate the `T` in `Guard<T>`, which has to be
406        // explicitly provided for the static variable `GUARD`.
407        // Instead, we currently fix `T` to be `f64`, and ensure all implementations of `Diff` returns `f64`.
408        // Here are some related language limitations:
409        // - Although `typeof` is a reserved keyword in Rust, it is never implemented. See <https://stackoverflow.com/questions/64890774>.
410        // - Rust does not, and explicitly would not (see https://doc.rust-lang.org/reference/items/static-items.html#statics--generics), support generic static variable.
411        // - Type inference is not performed for static variable, i.e. `Guard<_>` is not allowed.
412        // - Some form of existential type can help, but that's only available in nightly Rust under feature `type_alias_impl_trait`.
413        //
414        // Other approaches I can think of either requires dynamic type tagging that has
415        // runtime overhead, or requires the user of the macro to explicitly provide the type,
416        // which is really not ergonomic and deviate from the APIs from other SDKs.
417        let diff = $crate::assert::guidance::Diff::diff(&left, right);
418        type Guard<T> = $crate::assert::guidance::Guard<$maximize, T>;
419        // TODO: Waiting for [type_alias_impl_trait](https://github.com/rust-lang/rust/issues/63063) to stabilize...
420        // type Distance = impl Minimal;
421        type Distance = f64;
422        static GUARD: Guard<Distance> = Guard::init();
423        if GUARD.should_emit(diff) {
424            let guidance_data = $crate::serde_json::json!({ "left": left, "right": right });
425            $crate::guidance_helper!($crate::assert::guidance::GuidanceType::Numeric, $message, $maximize, guidance_data);
426        }
427    }};
428}
429
430#[cfg(not(feature = "full"))]
431#[doc(hidden)]
432#[macro_export]
433macro_rules! numeric_guidance_helper {
434    ($assert:path, $op:tt, $maximize:literal, $left:expr, $right:expr, $message:expr$(, $details:expr)?) => {{
435        // `$message` must be const evaluable
436        const _: &str = $message;
437        $assert!($left $op $right, $message$(, $details)?);
438    }};
439}
440
441#[cfg(feature = "full")]
442#[doc(hidden)]
443#[macro_export]
444macro_rules! boolean_guidance_helper {
445    ($assert:path, $all:literal, {$($name:ident: $cond:expr),*}, $message:expr$(, $details:expr)?) => {{
446        let details = &$crate::serde_json::Value::Null;
447        $(let details = $details;)?
448        let (cond, guidance_data) = {
449            $(let $name = $cond;)*
450            (
451                if $all { true $(&& $name)* } else { false $(|| $name)* },
452                $crate::serde_json::json!({$(::std::stringify!($name): $name),*})
453            )
454        };
455        let details = $crate::details::WithGuidance {
456            details,
457            guidance: || guidance_data.clone(),
458            context: $message,
459        };
460        $assert!(cond, $message, &details);
461        $crate::guidance_helper!($crate::assert::guidance::GuidanceType::Boolean, $message, $all, guidance_data);
462    }};
463}
464
465#[cfg(not(feature = "full"))]
466#[doc(hidden)]
467#[macro_export]
468macro_rules! boolean_guidance_helper {
469    ($assert:path, $all:literal, {$($name:ident: $cond:expr),*}, $message:expr$(, $details:expr)?) => {{
470        let cond = {
471            $(let $name = $cond;)*
472            if $all { true $(&& $name)* } else { false $(|| $name)* }
473        };
474        $assert!(cond, $message$(, $details)?);
475    }};
476}
477
478/// `assert_always_greater_than(x, y, ...)` is mostly equivalent to
479/// `assert_always!(x > y, ...)`, except Antithesis has more visibility to the
480/// value of `x` and `y`, and the assertion details would be merged with
481/// `{"left": x, "right": y}`.
482///
483/// Ensure that non-const-evaluable messages are rejected.
484///
485/// ```
486/// use serde_json::json;
487/// const MESSAGE: &str = concat!("x", " over y");
488/// antithesis_sdk::assert_always_greater_than!(2, 1, MESSAGE, &json!({}));
489/// ```
490///
491/// ```compile_fail
492/// use serde_json::json;
493/// antithesis_sdk::assert_always_greater_than!(2, 1, format!("{}", "x over y"), &json!({}));
494/// ```
495#[macro_export]
496macro_rules! assert_always_greater_than {
497    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
498        $crate::numeric_guidance_helper!($crate::assert_always, >, false, $left, $right, $message$(, $details)?)
499    };
500    ($($rest:tt)*) => {
501        ::std::compile_error!(
502r#"Invalid syntax when calling macro `assert_always_greater_than`.
503Example usage:
504    `assert_always_greater_than!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
505"#
506        );
507    };
508}
509
510/// `assert_always_greater_than_or_equal_to(x, y, ...)` is mostly equivalent to
511/// `assert_always!(x >= y, ...)`, except Antithesis has more visibility to the
512/// value of `x` and `y`, and the assertion details would be merged with
513/// `{"left": x, "right": y}`.
514#[macro_export]
515macro_rules! assert_always_greater_than_or_equal_to {
516    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
517        $crate::numeric_guidance_helper!($crate::assert_always, >=, false, $left, $right, $message$(, $details)?)
518    };
519    ($($rest:tt)*) => {
520        ::std::compile_error!(
521r#"Invalid syntax when calling macro `assert_always_greater_than_or_equal_to`.
522Example usage:
523    `assert_always_greater_than_or_equal_to!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
524"#
525        );
526    };
527}
528
529/// `assert_always_less_than(x, y, ...)` is mostly equivalent to
530/// `assert_always!(x < y, ...)`, except Antithesis has more visibility to the
531/// value of `x` and `y`, and the assertion details would be merged with
532/// `{"left": x, "right": y}`.
533#[macro_export]
534macro_rules! assert_always_less_than {
535    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
536        $crate::numeric_guidance_helper!($crate::assert_always, <, true, $left, $right, $message$(, $details)?)
537    };
538    ($($rest:tt)*) => {
539        ::std::compile_error!(
540r#"Invalid syntax when calling macro `assert_always_less_than`.
541Example usage:
542    `assert_always_less_than!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
543"#
544        );
545    };
546}
547
548/// `assert_always_less_than_or_equal_to(x, y, ...)` is mostly equivalent to
549/// `assert_always!(x <= y, ...)`, except Antithesis has more visibility to the
550/// value of `x` and `y`, and the assertion details would be merged with
551/// `{"left": x, "right": y}`.
552#[macro_export]
553macro_rules! assert_always_less_than_or_equal_to {
554    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
555        $crate::numeric_guidance_helper!($crate::assert_always, <=, true, $left, $right, $message$(, $details)?)
556    };
557    ($($rest:tt)*) => {
558        ::std::compile_error!(
559r#"Invalid syntax when calling macro `assert_always_less_than_or_equal_to`.
560Example usage:
561    `assert_always_less_than_or_equal_to!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
562"#
563        );
564    };
565}
566
567/// `assert_sometimes_greater_than(x, y, ...)` is mostly equivalent to
568/// `assert_sometimes!(x > y, ...)`, except Antithesis has more visibility to
569/// the value of `x` and `y`, and the assertion details would be merged with
570/// `{"left": x, "right": y}`.
571#[macro_export]
572macro_rules! assert_sometimes_greater_than {
573    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
574        $crate::numeric_guidance_helper!($crate::assert_sometimes, >, true, $left, $right, $message$(, $details)?)
575    };
576    ($($rest:tt)*) => {
577        ::std::compile_error!(
578r#"Invalid syntax when calling macro `assert_sometimes_greater_than`.
579Example usage:
580    `assert_sometimes_greater_than!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
581"#
582        );
583    };
584}
585
586/// `assert_sometimes_greater_than_or_equal_to(x, y, ...)` is mostly equivalent
587/// to `assert_sometimes!(x >= y, ...)`, except Antithesis has more visibility
588/// to the value of `x` and `y`, and the assertion details would be merged with
589/// `{"left": x, "right": y}`.
590#[macro_export]
591macro_rules! assert_sometimes_greater_than_or_equal_to {
592    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
593        $crate::numeric_guidance_helper!($crate::assert_sometimes, >=, true, $left, $right, $message$(, $details)?)
594    };
595    ($($rest:tt)*) => {
596        ::std::compile_error!(
597r#"Invalid syntax when calling macro `assert_sometimes_greater_than_or_equal_to`.
598Example usage:
599    `assert_sometimes_greater_than_or_equal_to!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
600"#
601        );
602    };
603}
604
605/// `assert_sometimes_less_than(x, y, ...)` is mostly equivalent to
606/// `assert_sometimes!(x < y, ...)`, except Antithesis has more visibility to
607/// the value of `x` and `y`, and the assertion details would be merged with
608/// `{"left": x, "right": y}`.
609#[macro_export]
610macro_rules! assert_sometimes_less_than {
611    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
612        $crate::numeric_guidance_helper!($crate::assert_sometimes, <, false, $left, $right, $message$(, $details)?)
613    };
614    ($($rest:tt)*) => {
615        ::std::compile_error!(
616r#"Invalid syntax when calling macro `assert_sometimes_less_than`.
617Example usage:
618    `assert_sometimes_less_than!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
619"#
620        );
621    };
622}
623
624/// `assert_sometimes_less_than_or_equal_to(x, y, ...)` is mostly equivalent to
625/// `assert_sometimes!(x <= y, ...)`, except Antithesis has more visibility to
626/// the value of `x` and `y`, and the assertion details would be merged with
627/// `{"left": x, "right": y}`.
628#[macro_export]
629macro_rules! assert_sometimes_less_than_or_equal_to {
630    ($left:expr, $right:expr, $message:expr$(, $details:expr)?) => {
631        $crate::numeric_guidance_helper!($crate::assert_sometimes, <=, false, $left, $right, $message$(, $details)?)
632    };
633    ($($rest:tt)*) => {
634        ::std::compile_error!(
635r#"Invalid syntax when calling macro `assert_sometimes_less_than_or_equal_to`.
636Example usage:
637    `assert_sometimes_less_than_or_equal_to!(left_expr, right_expr, "assertion message (const &'static str)", &details_json_value_expr)`
638"#
639        );
640    };
641}
642
643/// `assert_always_some({a: x, b: y, ...})` is similar to `assert_always(x || y
644/// || ...)`, except:
645/// - Antithesis has more visibility to the individual propositions.
646/// - There is no short-circuiting, so all of `x`, `y`, ... would be evaluated.
647/// - The assertion details would be merged with `{"a": x, "b": y, ...}`.
648///
649/// Ensure that non-const-evaluable messages are rejected.
650///
651/// ```
652/// use serde_json::json;
653/// const MESSAGE: &str = concat!("at least ", "one");
654/// antithesis_sdk::assert_always_some!({a: true, b: false}, MESSAGE, &json!({}));
655/// ```
656///
657/// ```compile_fail
658/// use serde_json::json;
659/// antithesis_sdk::assert_always_some!({a: true, b: false}, format!("{}", "at least one"), &json!({}));
660/// ```
661#[macro_export]
662macro_rules! assert_always_some {
663    ({$($($name:ident: $cond:expr),+ $(,)?)?}, $message:expr$(, $details:expr)?) => {
664        $crate::boolean_guidance_helper!($crate::assert_always, false, {$($($name: $cond),+)?}, $message$(, $details)?);
665    };
666    ($($rest:tt)*) => {
667        ::std::compile_error!(
668r#"Invalid syntax when calling macro `assert_always_some`.
669Example usage:
670    `assert_always_some!({field1: cond1, field2: cond2, ...}, "assertion message (const &'static str)", &details_json_value_expr)`
671"#
672        );
673    };
674}
675
676/// `assert_sometimes_all({a: x, b: y, ...})` is similar to
677/// `assert_sometimes(x && y && ...)`, except:
678/// - Antithesis has more visibility to the individual propositions.
679/// - There is no short-circuiting, so all of `x`, `y`, ... would be evaluated.
680/// - The assertion details would be merged with `{"a": x, "b": y, ...}`.
681#[macro_export]
682macro_rules! assert_sometimes_all {
683    ({$($($name:ident: $cond:expr),+ $(,)?)?}, $message:expr$(, $details:expr)?) => {
684        $crate::boolean_guidance_helper!($crate::assert_sometimes, true, {$($($name: $cond),+)?}, $message$(, $details)?);
685    };
686    ($($rest:tt)*) => {
687        ::std::compile_error!(
688r#"Invalid syntax when calling macro `assert_sometimes_all`.
689Example usage:
690    `assert_sometimes_all!({field1: cond1, field2: cond2, ...}, "assertion message (const &'static str)", &details_json_value_expr)`
691"#
692        );
693    };
694}