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    // SAFETY: Hardcoded epsilon is intentional and appropriate here because:
307    // - 1e-15 is conservative for IEEE 754 double precision (machine epsilon ~2.22e-16)
308    // - This function is used for validation, not high-precision calculations
309    // - The epsilon prevents spurious failures due to floating-point representation
310    // - Making it configurable would complicate the API for minimal benefit
311    const EPSILON: f64 = 1e-15;
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<M>(map: &M, param: &str) -> anyhow::Result<()>
379where
380    M: MapLike,
381{
382    if !map.is_empty() {
383        anyhow::bail!(
384            "the '{param}' map `&<{}, {}>` was not empty",
385            std::any::type_name::<M::Key>(),
386            std::any::type_name::<M::Value>(),
387        );
388    }
389    Ok(())
390}
391
392/// Checks the map is **not** empty.
393///
394/// # Errors
395///
396/// Returns an error if the validation check fails.
397#[inline(always)]
398pub fn check_map_not_empty<M>(map: &M, param: &str) -> anyhow::Result<()>
399where
400    M: MapLike,
401{
402    if map.is_empty() {
403        anyhow::bail!(
404            "the '{param}' map `&<{}, {}>` was empty",
405            std::any::type_name::<M::Key>(),
406            std::any::type_name::<M::Value>(),
407        );
408    }
409    Ok(())
410}
411
412/// Checks the `key` is **not** in the `map`.
413///
414/// # Errors
415///
416/// Returns an error if the validation check fails.
417#[inline(always)]
418pub fn check_key_not_in_map<M>(
419    key: &M::Key,
420    map: &M,
421    key_name: &str,
422    map_name: &str,
423) -> anyhow::Result<()>
424where
425    M: MapLike,
426{
427    if map.contains_key(key) {
428        anyhow::bail!(
429            "the '{key_name}' key {key} was already in the '{map_name}' map `&<{}, {}>`",
430            std::any::type_name::<M::Key>(),
431            std::any::type_name::<M::Value>(),
432        );
433    }
434    Ok(())
435}
436
437/// Checks the `key` is in the `map`.
438///
439/// # Errors
440///
441/// Returns an error if the validation check fails.
442#[inline(always)]
443pub fn check_key_in_map<M>(
444    key: &M::Key,
445    map: &M,
446    key_name: &str,
447    map_name: &str,
448) -> anyhow::Result<()>
449where
450    M: MapLike,
451{
452    if !map.contains_key(key) {
453        anyhow::bail!(
454            "the '{key_name}' key {key} was not in the '{map_name}' map `&<{}, {}>`",
455            std::any::type_name::<M::Key>(),
456            std::any::type_name::<M::Value>(),
457        );
458    }
459    Ok(())
460}
461
462/// Checks the `member` is **not** in the `set`.
463///
464/// # Errors
465///
466/// Returns an error if the validation check fails.
467#[inline(always)]
468pub fn check_member_not_in_set<S>(
469    member: &S::Item,
470    set: &S,
471    member_name: &str,
472    set_name: &str,
473) -> anyhow::Result<()>
474where
475    S: SetLike,
476{
477    if set.contains(member) {
478        anyhow::bail!(
479            "the '{member_name}' member was already in the '{set_name}' set `&<{}>`",
480            std::any::type_name::<S::Item>(),
481        );
482    }
483    Ok(())
484}
485
486/// Checks the `member` is in the `set`.
487///
488/// # Errors
489///
490/// Returns an error if the validation check fails.
491#[inline(always)]
492pub fn check_member_in_set<S>(
493    member: &S::Item,
494    set: &S,
495    member_name: &str,
496    set_name: &str,
497) -> anyhow::Result<()>
498where
499    S: SetLike,
500{
501    if !set.contains(member) {
502        anyhow::bail!(
503            "the '{member_name}' member was not in the '{set_name}' set `&<{}>`",
504            std::any::type_name::<S::Item>(),
505        );
506    }
507    Ok(())
508}
509
510////////////////////////////////////////////////////////////////////////////////
511// Tests
512////////////////////////////////////////////////////////////////////////////////
513#[cfg(test)]
514mod tests {
515    use std::{
516        collections::{HashMap, HashSet},
517        fmt::Display,
518    };
519
520    use rstest::rstest;
521
522    use super::*;
523
524    #[rstest]
525    #[case(false, false)]
526    #[case(true, true)]
527    fn test_check_predicate_true(#[case] predicate: bool, #[case] expected: bool) {
528        let result = check_predicate_true(predicate, "the predicate was false").is_ok();
529        assert_eq!(result, expected);
530    }
531
532    #[rstest]
533    #[case(false, true)]
534    #[case(true, false)]
535    fn test_check_predicate_false(#[case] predicate: bool, #[case] expected: bool) {
536        let result = check_predicate_false(predicate, "the predicate was true").is_ok();
537        assert_eq!(result, expected);
538    }
539
540    #[rstest]
541    #[case("a")]
542    #[case(" ")] // <-- whitespace is allowed
543    #[case("  ")] // <-- multiple whitespace is allowed
544    #[case("🦀")] // <-- non-ASCII is allowed
545    #[case(" a")]
546    #[case("a ")]
547    #[case("abc")]
548    fn test_check_nonempty_string_with_valid_values(#[case] s: &str) {
549        assert!(check_nonempty_string(s, "value").is_ok());
550    }
551
552    #[rstest]
553    #[case("")] // empty string
554    fn test_check_nonempty_string_with_invalid_values(#[case] s: &str) {
555        assert!(check_nonempty_string(s, "value").is_err());
556    }
557
558    #[rstest]
559    #[case(" a")]
560    #[case("a ")]
561    #[case("a a")]
562    #[case(" a ")]
563    #[case("abc")]
564    fn test_check_valid_string_with_valid_value(#[case] s: &str) {
565        assert!(check_valid_string(s, "value").is_ok());
566    }
567
568    #[rstest]
569    #[case("")] // <-- empty string
570    #[case(" ")] // <-- whitespace-only
571    #[case("  ")] // <-- whitespace-only string
572    #[case("🦀")] // <-- contains non-ASCII char
573    fn test_check_valid_string_with_invalid_values(#[case] s: &str) {
574        assert!(check_valid_string(s, "value").is_err());
575    }
576
577    #[rstest]
578    #[case(None)]
579    #[case(Some(" a"))]
580    #[case(Some("a "))]
581    #[case(Some("a a"))]
582    #[case(Some(" a "))]
583    #[case(Some("abc"))]
584    fn test_check_valid_string_optional_with_valid_value(#[case] s: Option<&str>) {
585        assert!(check_valid_string_optional(s, "value").is_ok());
586    }
587
588    #[rstest]
589    #[case("a", "a")]
590    fn test_check_string_contains_when_does_contain(#[case] s: &str, #[case] pat: &str) {
591        assert!(check_string_contains(s, pat, "value").is_ok());
592    }
593
594    #[rstest]
595    #[case("a", "b")]
596    fn test_check_string_contains_when_does_not_contain(#[case] s: &str, #[case] pat: &str) {
597        assert!(check_string_contains(s, pat, "value").is_err());
598    }
599
600    #[rstest]
601    #[case(0u8, 0u8, "left", "right", true)]
602    #[case(1u8, 1u8, "left", "right", true)]
603    #[case(0u8, 1u8, "left", "right", false)]
604    #[case(1u8, 0u8, "left", "right", false)]
605    #[case(10i32, 10i32, "left", "right", true)]
606    #[case(10i32, 20i32, "left", "right", false)]
607    #[case("hello", "hello", "left", "right", true)]
608    #[case("hello", "world", "left", "right", false)]
609    fn test_check_equal<T: PartialEq + Debug + Display>(
610        #[case] lhs: T,
611        #[case] rhs: T,
612        #[case] lhs_param: &str,
613        #[case] rhs_param: &str,
614        #[case] expected: bool,
615    ) {
616        let result = check_equal(&lhs, &rhs, lhs_param, rhs_param).is_ok();
617        assert_eq!(result, expected);
618    }
619
620    #[rstest]
621    #[case(0, 0, "left", "right", true)]
622    #[case(1, 1, "left", "right", true)]
623    #[case(0, 1, "left", "right", false)]
624    #[case(1, 0, "left", "right", false)]
625    fn test_check_equal_u8_when_equal(
626        #[case] lhs: u8,
627        #[case] rhs: u8,
628        #[case] lhs_param: &str,
629        #[case] rhs_param: &str,
630        #[case] expected: bool,
631    ) {
632        let result = check_equal_u8(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_usize_when_equal(
642        #[case] lhs: usize,
643        #[case] rhs: usize,
644        #[case] lhs_param: &str,
645        #[case] rhs_param: &str,
646        #[case] expected: bool,
647    ) {
648        let result = check_equal_usize(lhs, rhs, lhs_param, rhs_param).is_ok();
649        assert_eq!(result, expected);
650    }
651
652    #[rstest]
653    #[case(1, "value")]
654    fn test_check_positive_u64_when_positive(#[case] value: u64, #[case] param: &str) {
655        assert!(check_positive_u64(value, param).is_ok());
656    }
657
658    #[rstest]
659    #[case(0, "value")]
660    fn test_check_positive_u64_when_not_positive(#[case] value: u64, #[case] param: &str) {
661        assert!(check_positive_u64(value, param).is_err());
662    }
663
664    #[rstest]
665    #[case(1, "value")]
666    fn test_check_positive_i64_when_positive(#[case] value: i64, #[case] param: &str) {
667        assert!(check_positive_i64(value, param).is_ok());
668    }
669
670    #[rstest]
671    #[case(0, "value")]
672    #[case(-1, "value")]
673    fn test_check_positive_i64_when_not_positive(#[case] value: i64, #[case] param: &str) {
674        assert!(check_positive_i64(value, param).is_err());
675    }
676
677    #[rstest]
678    #[case(0.0, "value")]
679    #[case(1.0, "value")]
680    fn test_check_non_negative_f64_when_not_negative(#[case] value: f64, #[case] param: &str) {
681        assert!(check_non_negative_f64(value, param).is_ok());
682    }
683
684    #[rstest]
685    #[case(f64::NAN, "value")]
686    #[case(f64::INFINITY, "value")]
687    #[case(f64::NEG_INFINITY, "value")]
688    #[case(-0.1, "value")]
689    fn test_check_non_negative_f64_when_negative(#[case] value: f64, #[case] param: &str) {
690        assert!(check_non_negative_f64(value, param).is_err());
691    }
692
693    #[rstest]
694    #[case(0, 0, 0, "value")]
695    #[case(0, 0, 1, "value")]
696    #[case(1, 0, 1, "value")]
697    fn test_check_in_range_inclusive_u8_when_in_range(
698        #[case] value: u8,
699        #[case] l: u8,
700        #[case] r: u8,
701        #[case] desc: &str,
702    ) {
703        assert!(check_in_range_inclusive_u8(value, l, r, desc).is_ok());
704    }
705
706    #[rstest]
707    #[case(0, 1, 2, "value")]
708    #[case(3, 1, 2, "value")]
709    fn test_check_in_range_inclusive_u8_when_out_of_range(
710        #[case] value: u8,
711        #[case] l: u8,
712        #[case] r: u8,
713        #[case] param: &str,
714    ) {
715        assert!(check_in_range_inclusive_u8(value, l, r, param).is_err());
716    }
717
718    #[rstest]
719    #[case(0, 0, 0, "value")]
720    #[case(0, 0, 1, "value")]
721    #[case(1, 0, 1, "value")]
722    fn test_check_in_range_inclusive_u64_when_in_range(
723        #[case] value: u64,
724        #[case] l: u64,
725        #[case] r: u64,
726        #[case] param: &str,
727    ) {
728        assert!(check_in_range_inclusive_u64(value, l, r, param).is_ok());
729    }
730
731    #[rstest]
732    #[case(0, 1, 2, "value")]
733    #[case(3, 1, 2, "value")]
734    fn test_check_in_range_inclusive_u64_when_out_of_range(
735        #[case] value: u64,
736        #[case] l: u64,
737        #[case] r: u64,
738        #[case] param: &str,
739    ) {
740        assert!(check_in_range_inclusive_u64(value, l, r, param).is_err());
741    }
742
743    #[rstest]
744    #[case(0, 0, 0, "value")]
745    #[case(0, 0, 1, "value")]
746    #[case(1, 0, 1, "value")]
747    fn test_check_in_range_inclusive_i64_when_in_range(
748        #[case] value: i64,
749        #[case] l: i64,
750        #[case] r: i64,
751        #[case] param: &str,
752    ) {
753        assert!(check_in_range_inclusive_i64(value, l, r, param).is_ok());
754    }
755
756    #[rstest]
757    #[case(0.0, 0.0, 0.0, "value")]
758    #[case(0.0, 0.0, 1.0, "value")]
759    #[case(1.0, 0.0, 1.0, "value")]
760    fn test_check_in_range_inclusive_f64_when_in_range(
761        #[case] value: f64,
762        #[case] l: f64,
763        #[case] r: f64,
764        #[case] param: &str,
765    ) {
766        assert!(check_in_range_inclusive_f64(value, l, r, param).is_ok());
767    }
768
769    #[rstest]
770    #[case(-1e16, 0.0, 0.0, "value")]
771    #[case(1.0 + 1e16, 0.0, 1.0, "value")]
772    fn test_check_in_range_inclusive_f64_when_out_of_range(
773        #[case] value: f64,
774        #[case] l: f64,
775        #[case] r: f64,
776        #[case] param: &str,
777    ) {
778        assert!(check_in_range_inclusive_f64(value, l, r, param).is_err());
779    }
780
781    #[rstest]
782    #[case(0, 1, 2, "value")]
783    #[case(3, 1, 2, "value")]
784    fn test_check_in_range_inclusive_i64_when_out_of_range(
785        #[case] value: i64,
786        #[case] l: i64,
787        #[case] r: i64,
788        #[case] param: &str,
789    ) {
790        assert!(check_in_range_inclusive_i64(value, l, r, param).is_err());
791    }
792
793    #[rstest]
794    #[case(0, 0, 0, "value")]
795    #[case(0, 0, 1, "value")]
796    #[case(1, 0, 1, "value")]
797    fn test_check_in_range_inclusive_usize_when_in_range(
798        #[case] value: usize,
799        #[case] l: usize,
800        #[case] r: usize,
801        #[case] param: &str,
802    ) {
803        assert!(check_in_range_inclusive_usize(value, l, r, param).is_ok());
804    }
805
806    #[rstest]
807    #[case(0, 1, 2, "value")]
808    #[case(3, 1, 2, "value")]
809    fn test_check_in_range_inclusive_usize_when_out_of_range(
810        #[case] value: usize,
811        #[case] l: usize,
812        #[case] r: usize,
813        #[case] param: &str,
814    ) {
815        assert!(check_in_range_inclusive_usize(value, l, r, param).is_err());
816    }
817
818    #[rstest]
819    #[case(vec![], true)]
820    #[case(vec![1_u8], false)]
821    fn test_check_slice_empty(#[case] collection: Vec<u8>, #[case] expected: bool) {
822        let result = check_slice_empty(collection.as_slice(), "param").is_ok();
823        assert_eq!(result, expected);
824    }
825
826    #[rstest]
827    #[case(vec![], false)]
828    #[case(vec![1_u8], true)]
829    fn test_check_slice_not_empty(#[case] collection: Vec<u8>, #[case] expected: bool) {
830        let result = check_slice_not_empty(collection.as_slice(), "param").is_ok();
831        assert_eq!(result, expected);
832    }
833
834    #[rstest]
835    #[case(HashMap::new(), true)]
836    #[case(HashMap::from([("A".to_string(), 1_u8)]), false)]
837    fn test_check_map_empty(#[case] map: HashMap<String, u8>, #[case] expected: bool) {
838        let result = check_map_empty(&map, "param").is_ok();
839        assert_eq!(result, expected);
840    }
841
842    #[rstest]
843    #[case(HashMap::new(), false)]
844    #[case(HashMap::from([("A".to_string(), 1_u8)]), true)]
845    fn test_check_map_not_empty(#[case] map: HashMap<String, u8>, #[case] expected: bool) {
846        let result = check_map_not_empty(&map, "param").is_ok();
847        assert_eq!(result, expected);
848    }
849
850    #[rstest]
851    #[case(&HashMap::<u32, u32>::new(), 5, "key", "map", true)] // empty map
852    #[case(&HashMap::from([(1, 10), (2, 20)]), 1, "key", "map", false)] // key exists
853    #[case(&HashMap::from([(1, 10), (2, 20)]), 5, "key", "map", true)] // key doesn't exist
854    fn test_check_key_not_in_map(
855        #[case] map: &HashMap<u32, u32>,
856        #[case] key: u32,
857        #[case] key_name: &str,
858        #[case] map_name: &str,
859        #[case] expected: bool,
860    ) {
861        let result = check_key_not_in_map(&key, map, key_name, map_name).is_ok();
862        assert_eq!(result, expected);
863    }
864
865    #[rstest]
866    #[case(&HashMap::<u32, u32>::new(), 5, "key", "map", false)] // empty map
867    #[case(&HashMap::from([(1, 10), (2, 20)]), 1, "key", "map", true)] // key exists
868    #[case(&HashMap::from([(1, 10), (2, 20)]), 5, "key", "map", false)] // key doesn't exist
869    fn test_check_key_in_map(
870        #[case] map: &HashMap<u32, u32>,
871        #[case] key: u32,
872        #[case] key_name: &str,
873        #[case] map_name: &str,
874        #[case] expected: bool,
875    ) {
876        let result = check_key_in_map(&key, map, key_name, map_name).is_ok();
877        assert_eq!(result, expected);
878    }
879
880    #[rstest]
881    #[case(&HashSet::<u32>::new(), 5, "member", "set", true)] // Empty set
882    #[case(&HashSet::from([1, 2]), 1, "member", "set", false)] // Member exists
883    #[case(&HashSet::from([1, 2]), 5, "member", "set", true)] // Member doesn't exist
884    fn test_check_member_not_in_set(
885        #[case] set: &HashSet<u32>,
886        #[case] member: u32,
887        #[case] member_name: &str,
888        #[case] set_name: &str,
889        #[case] expected: bool,
890    ) {
891        let result = check_member_not_in_set(&member, set, member_name, set_name).is_ok();
892        assert_eq!(result, expected);
893    }
894
895    #[rstest]
896    #[case(&HashSet::<u32>::new(), 5, "member", "set", false)] // Empty set
897    #[case(&HashSet::from([1, 2]), 1, "member", "set", true)] // Member exists
898    #[case(&HashSet::from([1, 2]), 5, "member", "set", false)] // Member doesn't exist
899    fn test_check_member_in_set(
900        #[case] set: &HashSet<u32>,
901        #[case] member: u32,
902        #[case] member_name: &str,
903        #[case] set_name: &str,
904        #[case] expected: bool,
905    ) {
906        let result = check_member_in_set(&member, set, member_name, set_name).is_ok();
907        assert_eq!(result, expected);
908    }
909}