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