nautilus_core/
correctness.rs

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