nautilus_core/
correctness.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Functions for correctness checks similar to the *design by contract* philosophy.
17//!
18//! This module provides validation checking of function or method conditions.
19//!
20//! A condition is a predicate which must be true just prior to the execution of
21//! some section of code - for correct behavior as per the design specification.
22//!
23//! An [`anyhow::Result`] is returned with a descriptive message when the
24//! condition check fails.
25
26use std::fmt::{Debug, Display};
27
28use crate::collections::{MapLike, SetLike};
29
30/// A message prefix that can be used with calls to `expect` or other assertion-related functions.
31///
32/// This constant provides a standard message that can be used to indicate a failure condition
33/// when a predicate or condition does not hold true. It is typically used in conjunction with
34/// functions like `expect` to provide a consistent error message.
35pub const FAILED: &str = "Condition failed";
36
37/// Checks the `predicate` is true.
38///
39/// # Errors
40///
41/// Returns an error if the validation check fails.
42#[inline(always)]
43pub fn check_predicate_true(predicate: bool, fail_msg: &str) -> anyhow::Result<()> {
44    if !predicate {
45        anyhow::bail!("{fail_msg}")
46    }
47    Ok(())
48}
49
50/// Checks the `predicate` is false.
51///
52/// # Errors
53///
54/// Returns an error if the validation check fails.
55#[inline(always)]
56pub fn check_predicate_false(predicate: bool, fail_msg: &str) -> anyhow::Result<()> {
57    if predicate {
58        anyhow::bail!("{fail_msg}")
59    }
60    Ok(())
61}
62
63/// Checks if the string `s` is not empty.
64///
65/// This function performs a basic check to ensure the string has at least one character.
66/// Unlike `check_valid_string`, it does not validate ASCII characters or check for whitespace.
67///
68/// # Errors
69///
70/// Returns an error if `s` is empty.
71#[inline(always)]
72pub fn check_nonempty_string<T: AsRef<str>>(s: T, param: &str) -> anyhow::Result<()> {
73    if s.as_ref().is_empty() {
74        anyhow::bail!("invalid string for '{param}', was empty");
75    }
76    Ok(())
77}
78
79/// Checks the string `s` has semantic meaning and contains only ASCII characters.
80///
81/// # Errors
82///
83/// Returns an error if:
84/// - `s` is an empty string.
85/// - `s` consists solely of whitespace characters.
86/// - `s` contains one or more non-ASCII characters.
87#[inline(always)]
88pub fn check_valid_string<T: AsRef<str>>(s: T, param: &str) -> anyhow::Result<()> {
89    let s = s.as_ref();
90
91    if s.is_empty() {
92        anyhow::bail!("invalid string for '{param}', was empty");
93    }
94
95    // Ensure string is only traversed once
96    let mut has_non_whitespace = false;
97    for c in s.chars() {
98        if !c.is_whitespace() {
99            has_non_whitespace = true;
100        }
101        if !c.is_ascii() {
102            anyhow::bail!("invalid string for '{param}' contained a non-ASCII char, was '{s}'");
103        }
104    }
105
106    if !has_non_whitespace {
107        anyhow::bail!("invalid string for '{param}', was all whitespace");
108    }
109
110    Ok(())
111}
112
113/// Checks the string `s` if Some, contains only ASCII characters and has semantic meaning.
114///
115/// # Errors
116///
117/// Returns an error if:
118/// - `s` is an empty string.
119/// - `s` consists solely of whitespace characters.
120/// - `s` contains one or more non-ASCII characters.
121#[inline(always)]
122pub fn check_valid_string_optional<T: AsRef<str>>(s: Option<T>, param: &str) -> anyhow::Result<()> {
123    if let Some(s) = s {
124        check_valid_string(s, param)?;
125    }
126    Ok(())
127}
128
129/// Checks the string `s` contains the pattern `pat`.
130///
131/// # Errors
132///
133/// Returns an error if the validation check fails.
134#[inline(always)]
135pub fn check_string_contains<T: AsRef<str>>(s: T, pat: &str, param: &str) -> anyhow::Result<()> {
136    let s = s.as_ref();
137    if !s.contains(pat) {
138        anyhow::bail!("invalid string for '{param}' did not contain '{pat}', was '{s}'")
139    }
140    Ok(())
141}
142
143/// Checks the values are equal.
144///
145/// # Errors
146///
147/// Returns an error if the validation check fails.
148#[inline(always)]
149pub fn check_equal<T: PartialEq + Debug + Display>(
150    lhs: &T,
151    rhs: &T,
152    lhs_param: &str,
153    rhs_param: &str,
154) -> anyhow::Result<()> {
155    if lhs != rhs {
156        anyhow::bail!("'{lhs_param}' value of {lhs} was not equal to '{rhs_param}' value of {rhs}");
157    }
158    Ok(())
159}
160
161/// Checks the `u8` values are equal.
162///
163/// # Errors
164///
165/// Returns an error if the validation check fails.
166#[inline(always)]
167pub fn check_equal_u8(lhs: u8, rhs: u8, lhs_param: &str, rhs_param: &str) -> anyhow::Result<()> {
168    if lhs != rhs {
169        anyhow::bail!("'{lhs_param}' u8 of {lhs} was not equal to '{rhs_param}' u8 of {rhs}")
170    }
171    Ok(())
172}
173
174/// Checks the `usize` values are equal.
175///
176/// # Errors
177///
178/// Returns an error if the validation check fails.
179#[inline(always)]
180pub fn check_equal_usize(
181    lhs: usize,
182    rhs: usize,
183    lhs_param: &str,
184    rhs_param: &str,
185) -> anyhow::Result<()> {
186    if lhs != rhs {
187        anyhow::bail!("'{lhs_param}' usize of {lhs} was not equal to '{rhs_param}' usize of {rhs}")
188    }
189    Ok(())
190}
191
192/// Checks the `u64` value is positive (> 0).
193///
194/// # Errors
195///
196/// Returns an error if the validation check fails.
197#[inline(always)]
198pub fn check_positive_u64(value: u64, param: &str) -> anyhow::Result<()> {
199    if value == 0 {
200        anyhow::bail!("invalid u64 for '{param}' not positive, was {value}")
201    }
202    Ok(())
203}
204
205/// Checks the `u128` value is positive (> 0).
206///
207/// # Errors
208///
209/// Returns an error if the validation check fails.
210#[inline(always)]
211pub fn check_positive_u128(value: u128, param: &str) -> anyhow::Result<()> {
212    if value == 0 {
213        anyhow::bail!("invalid u128 for '{param}' not positive, was {value}")
214    }
215    Ok(())
216}
217
218/// Checks the `i64` value is positive (> 0).
219///
220/// # Errors
221///
222/// Returns an error if the validation check fails.
223#[inline(always)]
224pub fn check_positive_i64(value: i64, param: &str) -> anyhow::Result<()> {
225    if value <= 0 {
226        anyhow::bail!("invalid i64 for '{param}' not positive, was {value}")
227    }
228    Ok(())
229}
230
231/// Checks the `i64` value is positive (> 0).
232///
233/// # Errors
234///
235/// Returns an error if the validation check fails.
236#[inline(always)]
237pub fn check_positive_i128(value: i128, param: &str) -> anyhow::Result<()> {
238    if value <= 0 {
239        anyhow::bail!("invalid i128 for '{param}' not positive, was {value}")
240    }
241    Ok(())
242}
243
244/// Checks the `f64` value is non-negative (< 0).
245///
246/// # Errors
247///
248/// Returns an error if the validation check fails.
249#[inline(always)]
250pub fn check_non_negative_f64(value: f64, param: &str) -> anyhow::Result<()> {
251    if value.is_nan() || value.is_infinite() {
252        anyhow::bail!("invalid f64 for '{param}', was {value}")
253    }
254    if value < 0.0 {
255        anyhow::bail!("invalid f64 for '{param}' negative, was {value}")
256    }
257    Ok(())
258}
259
260/// Checks the `u8` value is in range [`l`, `r`] (inclusive).
261///
262/// # Errors
263///
264/// Returns an error if the validation check fails.
265#[inline(always)]
266pub fn check_in_range_inclusive_u8(value: u8, l: u8, r: u8, param: &str) -> anyhow::Result<()> {
267    if value < l || value > r {
268        anyhow::bail!("invalid u8 for '{param}' not in range [{l}, {r}], was {value}")
269    }
270    Ok(())
271}
272
273/// Checks the `u64` value is range [`l`, `r`] (inclusive).
274///
275/// # Errors
276///
277/// Returns an error if the validation check fails.
278#[inline(always)]
279pub fn check_in_range_inclusive_u64(value: u64, l: u64, r: u64, param: &str) -> anyhow::Result<()> {
280    if value < l || value > r {
281        anyhow::bail!("invalid u64 for '{param}' not in range [{l}, {r}], was {value}")
282    }
283    Ok(())
284}
285
286/// Checks the `i64` value is in range [`l`, `r`] (inclusive).
287///
288/// # Errors
289///
290/// Returns an error if the validation check fails.
291#[inline(always)]
292pub fn check_in_range_inclusive_i64(value: i64, l: i64, r: i64, param: &str) -> anyhow::Result<()> {
293    if value < l || value > r {
294        anyhow::bail!("invalid i64 for '{param}' not in range [{l}, {r}], was {value}")
295    }
296    Ok(())
297}
298
299/// Checks the `f64` value is in range [`l`, `r`] (inclusive).
300///
301/// # Errors
302///
303/// Returns an error if the validation check fails.
304#[inline(always)]
305pub fn check_in_range_inclusive_f64(value: f64, l: f64, r: f64, param: &str) -> anyhow::Result<()> {
306    const EPSILON: f64 = 1e-15; // Epsilon to account for floating-point precision issues
307
308    if value.is_nan() || value.is_infinite() {
309        anyhow::bail!("invalid f64 for '{param}', was {value}")
310    }
311    if value < l - EPSILON || value > r + EPSILON {
312        anyhow::bail!("invalid f64 for '{param}' not in range [{l}, {r}], was {value}")
313    }
314    Ok(())
315}
316
317/// Checks the `usize` value is in range [`l`, `r`] (inclusive).
318///
319/// # Errors
320///
321/// Returns an error if the validation check fails.
322#[inline(always)]
323pub fn check_in_range_inclusive_usize(
324    value: usize,
325    l: usize,
326    r: usize,
327    param: &str,
328) -> anyhow::Result<()> {
329    if value < l || value > r {
330        anyhow::bail!("invalid usize for '{param}' not in range [{l}, {r}], was {value}")
331    }
332    Ok(())
333}
334
335/// Checks the slice is empty.
336///
337/// # Errors
338///
339/// Returns an error if the validation check fails.
340#[inline(always)]
341pub fn check_slice_empty<T>(slice: &[T], param: &str) -> anyhow::Result<()> {
342    if !slice.is_empty() {
343        anyhow::bail!(
344            "the '{param}' slice `&[{}]` was not empty",
345            std::any::type_name::<T>()
346        )
347    }
348    Ok(())
349}
350
351/// Checks the slice is **not** empty.
352///
353/// # Errors
354///
355/// Returns an error if the validation check fails.
356#[inline(always)]
357pub fn check_slice_not_empty<T>(slice: &[T], param: &str) -> anyhow::Result<()> {
358    if slice.is_empty() {
359        anyhow::bail!(
360            "the '{param}' slice `&[{}]` was empty",
361            std::any::type_name::<T>()
362        )
363    }
364    Ok(())
365}
366
367/// Checks the hashmap is empty.
368///
369/// # Errors
370///
371/// Returns an error if the validation check fails.
372#[inline(always)]
373pub fn check_map_empty<M>(map: &M, param: &str) -> anyhow::Result<()>
374where
375    M: MapLike,
376{
377    if !map.is_empty() {
378        anyhow::bail!(
379            "the '{param}' map `&<{}, {}>` was not empty",
380            std::any::type_name::<M::Key>(),
381            std::any::type_name::<M::Value>(),
382        );
383    }
384    Ok(())
385}
386
387/// Checks the map is **not** empty.
388///
389/// # Errors
390///
391/// Returns an error if the validation check fails.
392#[inline(always)]
393pub fn check_map_not_empty<M>(map: &M, param: &str) -> anyhow::Result<()>
394where
395    M: MapLike,
396{
397    if map.is_empty() {
398        anyhow::bail!(
399            "the '{param}' map `&<{}, {}>` was empty",
400            std::any::type_name::<M::Key>(),
401            std::any::type_name::<M::Value>(),
402        );
403    }
404    Ok(())
405}
406
407/// Checks the `key` is **not** in the `map`.
408///
409/// # Errors
410///
411/// Returns an error if the validation check fails.
412#[inline(always)]
413pub fn check_key_not_in_map<M>(
414    key: &M::Key,
415    map: &M,
416    key_name: &str,
417    map_name: &str,
418) -> anyhow::Result<()>
419where
420    M: MapLike,
421{
422    if map.contains_key(key) {
423        anyhow::bail!(
424            "the '{key_name}' key {key} was already in the '{map_name}' map `&<{}, {}>`",
425            std::any::type_name::<M::Key>(),
426            std::any::type_name::<M::Value>(),
427        );
428    }
429    Ok(())
430}
431
432/// Checks the `key` is in the `map`.
433///
434/// # Errors
435///
436/// Returns an error if the validation check fails.
437#[inline(always)]
438pub fn check_key_in_map<M>(
439    key: &M::Key,
440    map: &M,
441    key_name: &str,
442    map_name: &str,
443) -> anyhow::Result<()>
444where
445    M: MapLike,
446{
447    if !map.contains_key(key) {
448        anyhow::bail!(
449            "the '{key_name}' key {key} was not in the '{map_name}' map `&<{}, {}>`",
450            std::any::type_name::<M::Key>(),
451            std::any::type_name::<M::Value>(),
452        );
453    }
454    Ok(())
455}
456
457/// Checks the `member` is **not** in the `set`.
458///
459/// # Errors
460///
461/// Returns an error if the validation check fails.
462#[inline(always)]
463pub fn check_member_not_in_set<S>(
464    member: &S::Item,
465    set: &S,
466    member_name: &str,
467    set_name: &str,
468) -> anyhow::Result<()>
469where
470    S: SetLike,
471{
472    if set.contains(member) {
473        anyhow::bail!(
474            "the '{member_name}' member was already in the '{set_name}' set `&<{}>`",
475            std::any::type_name::<S::Item>(),
476        );
477    }
478    Ok(())
479}
480
481/// Checks the `member` is in the `set`.
482///
483/// # Errors
484///
485/// Returns an error if the validation check fails.
486#[inline(always)]
487pub fn check_member_in_set<S>(
488    member: &S::Item,
489    set: &S,
490    member_name: &str,
491    set_name: &str,
492) -> anyhow::Result<()>
493where
494    S: SetLike,
495{
496    if !set.contains(member) {
497        anyhow::bail!(
498            "the '{member_name}' member was not in the '{set_name}' set `&<{}>`",
499            std::any::type_name::<S::Item>(),
500        );
501    }
502    Ok(())
503}
504
505////////////////////////////////////////////////////////////////////////////////
506// Tests
507////////////////////////////////////////////////////////////////////////////////
508#[cfg(test)]
509mod tests {
510    use std::{
511        collections::{HashMap, HashSet},
512        fmt::Display,
513    };
514
515    use rstest::rstest;
516
517    use super::*;
518
519    #[rstest]
520    #[case(false, false)]
521    #[case(true, true)]
522    fn test_check_predicate_true(#[case] predicate: bool, #[case] expected: bool) {
523        let result = check_predicate_true(predicate, "the predicate was false").is_ok();
524        assert_eq!(result, expected);
525    }
526
527    #[rstest]
528    #[case(false, true)]
529    #[case(true, false)]
530    fn test_check_predicate_false(#[case] predicate: bool, #[case] expected: bool) {
531        let result = check_predicate_false(predicate, "the predicate was true").is_ok();
532        assert_eq!(result, expected);
533    }
534
535    #[rstest]
536    #[case("a")]
537    #[case(" ")] // <-- whitespace is allowed
538    #[case("  ")] // <-- multiple whitespace is allowed
539    #[case("🦀")] // <-- non-ASCII is allowed
540    #[case(" a")]
541    #[case("a ")]
542    #[case("abc")]
543    fn test_check_nonempty_string_with_valid_values(#[case] s: &str) {
544        assert!(check_nonempty_string(s, "value").is_ok());
545    }
546
547    #[rstest]
548    #[case("")] // empty string
549    fn test_check_nonempty_string_with_invalid_values(#[case] s: &str) {
550        assert!(check_nonempty_string(s, "value").is_err());
551    }
552
553    #[rstest]
554    #[case(" a")]
555    #[case("a ")]
556    #[case("a a")]
557    #[case(" a ")]
558    #[case("abc")]
559    fn test_check_valid_string_with_valid_value(#[case] s: &str) {
560        assert!(check_valid_string(s, "value").is_ok());
561    }
562
563    #[rstest]
564    #[case("")] // <-- empty string
565    #[case(" ")] // <-- whitespace-only
566    #[case("  ")] // <-- whitespace-only string
567    #[case("🦀")] // <-- contains non-ASCII char
568    fn test_check_valid_string_with_invalid_values(#[case] s: &str) {
569        assert!(check_valid_string(s, "value").is_err());
570    }
571
572    #[rstest]
573    #[case(None)]
574    #[case(Some(" a"))]
575    #[case(Some("a "))]
576    #[case(Some("a a"))]
577    #[case(Some(" a "))]
578    #[case(Some("abc"))]
579    fn test_check_valid_string_optional_with_valid_value(#[case] s: Option<&str>) {
580        assert!(check_valid_string_optional(s, "value").is_ok());
581    }
582
583    #[rstest]
584    #[case("a", "a")]
585    fn test_check_string_contains_when_does_contain(#[case] s: &str, #[case] pat: &str) {
586        assert!(check_string_contains(s, pat, "value").is_ok());
587    }
588
589    #[rstest]
590    #[case("a", "b")]
591    fn test_check_string_contains_when_does_not_contain(#[case] s: &str, #[case] pat: &str) {
592        assert!(check_string_contains(s, pat, "value").is_err());
593    }
594
595    #[rstest]
596    #[case(0u8, 0u8, "left", "right", true)]
597    #[case(1u8, 1u8, "left", "right", true)]
598    #[case(0u8, 1u8, "left", "right", false)]
599    #[case(1u8, 0u8, "left", "right", false)]
600    #[case(10i32, 10i32, "left", "right", true)]
601    #[case(10i32, 20i32, "left", "right", false)]
602    #[case("hello", "hello", "left", "right", true)]
603    #[case("hello", "world", "left", "right", false)]
604    fn test_check_equal<T: PartialEq + Debug + Display>(
605        #[case] lhs: T,
606        #[case] rhs: T,
607        #[case] lhs_param: &str,
608        #[case] rhs_param: &str,
609        #[case] expected: bool,
610    ) {
611        let result = check_equal(&lhs, &rhs, lhs_param, rhs_param).is_ok();
612        assert_eq!(result, expected);
613    }
614
615    #[rstest]
616    #[case(0, 0, "left", "right", true)]
617    #[case(1, 1, "left", "right", true)]
618    #[case(0, 1, "left", "right", false)]
619    #[case(1, 0, "left", "right", false)]
620    fn test_check_equal_u8_when_equal(
621        #[case] lhs: u8,
622        #[case] rhs: u8,
623        #[case] lhs_param: &str,
624        #[case] rhs_param: &str,
625        #[case] expected: bool,
626    ) {
627        let result = check_equal_u8(lhs, rhs, lhs_param, rhs_param).is_ok();
628        assert_eq!(result, expected);
629    }
630
631    #[rstest]
632    #[case(0, 0, "left", "right", true)]
633    #[case(1, 1, "left", "right", true)]
634    #[case(0, 1, "left", "right", false)]
635    #[case(1, 0, "left", "right", false)]
636    fn test_check_equal_usize_when_equal(
637        #[case] lhs: usize,
638        #[case] rhs: usize,
639        #[case] lhs_param: &str,
640        #[case] rhs_param: &str,
641        #[case] expected: bool,
642    ) {
643        let result = check_equal_usize(lhs, rhs, lhs_param, rhs_param).is_ok();
644        assert_eq!(result, expected);
645    }
646
647    #[rstest]
648    #[case(1, "value")]
649    fn test_check_positive_u64_when_positive(#[case] value: u64, #[case] param: &str) {
650        assert!(check_positive_u64(value, param).is_ok());
651    }
652
653    #[rstest]
654    #[case(0, "value")]
655    fn test_check_positive_u64_when_not_positive(#[case] value: u64, #[case] param: &str) {
656        assert!(check_positive_u64(value, param).is_err());
657    }
658
659    #[rstest]
660    #[case(1, "value")]
661    fn test_check_positive_i64_when_positive(#[case] value: i64, #[case] param: &str) {
662        assert!(check_positive_i64(value, param).is_ok());
663    }
664
665    #[rstest]
666    #[case(0, "value")]
667    #[case(-1, "value")]
668    fn test_check_positive_i64_when_not_positive(#[case] value: i64, #[case] param: &str) {
669        assert!(check_positive_i64(value, param).is_err());
670    }
671
672    #[rstest]
673    #[case(0.0, "value")]
674    #[case(1.0, "value")]
675    fn test_check_non_negative_f64_when_not_negative(#[case] value: f64, #[case] param: &str) {
676        assert!(check_non_negative_f64(value, param).is_ok());
677    }
678
679    #[rstest]
680    #[case(f64::NAN, "value")]
681    #[case(f64::INFINITY, "value")]
682    #[case(f64::NEG_INFINITY, "value")]
683    #[case(-0.1, "value")]
684    fn test_check_non_negative_f64_when_negative(#[case] value: f64, #[case] param: &str) {
685        assert!(check_non_negative_f64(value, param).is_err());
686    }
687
688    #[rstest]
689    #[case(0, 0, 0, "value")]
690    #[case(0, 0, 1, "value")]
691    #[case(1, 0, 1, "value")]
692    fn test_check_in_range_inclusive_u8_when_in_range(
693        #[case] value: u8,
694        #[case] l: u8,
695        #[case] r: u8,
696        #[case] desc: &str,
697    ) {
698        assert!(check_in_range_inclusive_u8(value, l, r, desc).is_ok());
699    }
700
701    #[rstest]
702    #[case(0, 1, 2, "value")]
703    #[case(3, 1, 2, "value")]
704    fn test_check_in_range_inclusive_u8_when_out_of_range(
705        #[case] value: u8,
706        #[case] l: u8,
707        #[case] r: u8,
708        #[case] param: &str,
709    ) {
710        assert!(check_in_range_inclusive_u8(value, l, r, param).is_err());
711    }
712
713    #[rstest]
714    #[case(0, 0, 0, "value")]
715    #[case(0, 0, 1, "value")]
716    #[case(1, 0, 1, "value")]
717    fn test_check_in_range_inclusive_u64_when_in_range(
718        #[case] value: u64,
719        #[case] l: u64,
720        #[case] r: u64,
721        #[case] param: &str,
722    ) {
723        assert!(check_in_range_inclusive_u64(value, l, r, param).is_ok());
724    }
725
726    #[rstest]
727    #[case(0, 1, 2, "value")]
728    #[case(3, 1, 2, "value")]
729    fn test_check_in_range_inclusive_u64_when_out_of_range(
730        #[case] value: u64,
731        #[case] l: u64,
732        #[case] r: u64,
733        #[case] param: &str,
734    ) {
735        assert!(check_in_range_inclusive_u64(value, l, r, param).is_err());
736    }
737
738    #[rstest]
739    #[case(0, 0, 0, "value")]
740    #[case(0, 0, 1, "value")]
741    #[case(1, 0, 1, "value")]
742    fn test_check_in_range_inclusive_i64_when_in_range(
743        #[case] value: i64,
744        #[case] l: i64,
745        #[case] r: i64,
746        #[case] param: &str,
747    ) {
748        assert!(check_in_range_inclusive_i64(value, l, r, param).is_ok());
749    }
750
751    #[rstest]
752    #[case(0.0, 0.0, 0.0, "value")]
753    #[case(0.0, 0.0, 1.0, "value")]
754    #[case(1.0, 0.0, 1.0, "value")]
755    fn test_check_in_range_inclusive_f64_when_in_range(
756        #[case] value: f64,
757        #[case] l: f64,
758        #[case] r: f64,
759        #[case] param: &str,
760    ) {
761        assert!(check_in_range_inclusive_f64(value, l, r, param).is_ok());
762    }
763
764    #[rstest]
765    #[case(-1e16, 0.0, 0.0, "value")]
766    #[case(1.0 + 1e16, 0.0, 1.0, "value")]
767    fn test_check_in_range_inclusive_f64_when_out_of_range(
768        #[case] value: f64,
769        #[case] l: f64,
770        #[case] r: f64,
771        #[case] param: &str,
772    ) {
773        assert!(check_in_range_inclusive_f64(value, l, r, param).is_err());
774    }
775
776    #[rstest]
777    #[case(0, 1, 2, "value")]
778    #[case(3, 1, 2, "value")]
779    fn test_check_in_range_inclusive_i64_when_out_of_range(
780        #[case] value: i64,
781        #[case] l: i64,
782        #[case] r: i64,
783        #[case] param: &str,
784    ) {
785        assert!(check_in_range_inclusive_i64(value, l, r, param).is_err());
786    }
787
788    #[rstest]
789    #[case(0, 0, 0, "value")]
790    #[case(0, 0, 1, "value")]
791    #[case(1, 0, 1, "value")]
792    fn test_check_in_range_inclusive_usize_when_in_range(
793        #[case] value: usize,
794        #[case] l: usize,
795        #[case] r: usize,
796        #[case] param: &str,
797    ) {
798        assert!(check_in_range_inclusive_usize(value, l, r, param).is_ok());
799    }
800
801    #[rstest]
802    #[case(0, 1, 2, "value")]
803    #[case(3, 1, 2, "value")]
804    fn test_check_in_range_inclusive_usize_when_out_of_range(
805        #[case] value: usize,
806        #[case] l: usize,
807        #[case] r: usize,
808        #[case] param: &str,
809    ) {
810        assert!(check_in_range_inclusive_usize(value, l, r, param).is_err());
811    }
812
813    #[rstest]
814    #[case(vec![], true)]
815    #[case(vec![1_u8], false)]
816    fn test_check_slice_empty(#[case] collection: Vec<u8>, #[case] expected: bool) {
817        let result = check_slice_empty(collection.as_slice(), "param").is_ok();
818        assert_eq!(result, expected);
819    }
820
821    #[rstest]
822    #[case(vec![], false)]
823    #[case(vec![1_u8], true)]
824    fn test_check_slice_not_empty(#[case] collection: Vec<u8>, #[case] expected: bool) {
825        let result = check_slice_not_empty(collection.as_slice(), "param").is_ok();
826        assert_eq!(result, expected);
827    }
828
829    #[rstest]
830    #[case(HashMap::new(), true)]
831    #[case(HashMap::from([("A".to_string(), 1_u8)]), false)]
832    fn test_check_map_empty(#[case] map: HashMap<String, u8>, #[case] expected: bool) {
833        let result = check_map_empty(&map, "param").is_ok();
834        assert_eq!(result, expected);
835    }
836
837    #[rstest]
838    #[case(HashMap::new(), false)]
839    #[case(HashMap::from([("A".to_string(), 1_u8)]), true)]
840    fn test_check_map_not_empty(#[case] map: HashMap<String, u8>, #[case] expected: bool) {
841        let result = check_map_not_empty(&map, "param").is_ok();
842        assert_eq!(result, expected);
843    }
844
845    #[rstest]
846    #[case(&HashMap::<u32, u32>::new(), 5, "key", "map", true)] // empty map
847    #[case(&HashMap::from([(1, 10), (2, 20)]), 1, "key", "map", false)] // key exists
848    #[case(&HashMap::from([(1, 10), (2, 20)]), 5, "key", "map", true)] // key doesn't exist
849    fn test_check_key_not_in_map(
850        #[case] map: &HashMap<u32, u32>,
851        #[case] key: u32,
852        #[case] key_name: &str,
853        #[case] map_name: &str,
854        #[case] expected: bool,
855    ) {
856        let result = check_key_not_in_map(&key, map, key_name, map_name).is_ok();
857        assert_eq!(result, expected);
858    }
859
860    #[rstest]
861    #[case(&HashMap::<u32, u32>::new(), 5, "key", "map", false)] // empty map
862    #[case(&HashMap::from([(1, 10), (2, 20)]), 1, "key", "map", true)] // key exists
863    #[case(&HashMap::from([(1, 10), (2, 20)]), 5, "key", "map", false)] // key doesn't exist
864    fn test_check_key_in_map(
865        #[case] map: &HashMap<u32, u32>,
866        #[case] key: u32,
867        #[case] key_name: &str,
868        #[case] map_name: &str,
869        #[case] expected: bool,
870    ) {
871        let result = check_key_in_map(&key, map, key_name, map_name).is_ok();
872        assert_eq!(result, expected);
873    }
874
875    #[rstest]
876    #[case(&HashSet::<u32>::new(), 5, "member", "set", true)] // Empty set
877    #[case(&HashSet::from([1, 2]), 1, "member", "set", false)] // Member exists
878    #[case(&HashSet::from([1, 2]), 5, "member", "set", true)] // Member doesn't exist
879    fn test_check_member_not_in_set(
880        #[case] set: &HashSet<u32>,
881        #[case] member: u32,
882        #[case] member_name: &str,
883        #[case] set_name: &str,
884        #[case] expected: bool,
885    ) {
886        let result = check_member_not_in_set(&member, set, member_name, set_name).is_ok();
887        assert_eq!(result, expected);
888    }
889
890    #[rstest]
891    #[case(&HashSet::<u32>::new(), 5, "member", "set", false)] // Empty set
892    #[case(&HashSet::from([1, 2]), 1, "member", "set", true)] // Member exists
893    #[case(&HashSet::from([1, 2]), 5, "member", "set", false)] // Member doesn't exist
894    fn test_check_member_in_set(
895        #[case] set: &HashSet<u32>,
896        #[case] member: u32,
897        #[case] member_name: &str,
898        #[case] set_name: &str,
899        #[case] expected: bool,
900    ) {
901        let result = check_member_in_set(&member, set, member_name, set_name).is_ok();
902        assert_eq!(result, expected);
903    }
904}