1use std::fmt::{Debug, Display};
27
28use rust_decimal::Decimal;
29use thiserror::Error;
30
31use crate::collections::{MapLike, SetLike};
32
33pub const FAILED: &str = "Condition failed";
39
40#[derive(Clone, Debug, Error, Eq, PartialEq)]
42pub enum CorrectnessError {
43 #[error("{message}")]
45 PredicateViolation {
46 message: String,
48 },
49 #[error("invalid string for '{param}', was empty")]
51 EmptyString {
52 param: String,
54 },
55 #[error("invalid string for '{param}', was all whitespace")]
57 WhitespaceString {
58 param: String,
60 },
61 #[error("invalid string for '{param}' contained a non-ASCII char, was '{value}'")]
63 NonAsciiString {
64 param: String,
66 value: String,
68 },
69 #[error("invalid string for '{param}' did not contain '{pattern}', was '{value}'")]
71 MissingSubstring {
72 param: String,
74 pattern: String,
76 value: String,
78 },
79 #[error(
81 "'{lhs_param}' {type_name} of {lhs} was not equal to '{rhs_param}' {type_name} of {rhs}"
82 )]
83 EqualityMismatch {
84 lhs_param: String,
86 rhs_param: String,
88 lhs: String,
90 rhs: String,
92 type_name: &'static str,
94 },
95 #[error("invalid {type_name} for '{param}' not positive, was {value}")]
97 NotPositive {
98 param: String,
100 value: String,
102 type_name: &'static str,
104 },
105 #[error("invalid {type_name} for '{param}' negative, was {value}")]
107 NegativeValue {
108 param: String,
110 value: String,
112 type_name: &'static str,
114 },
115 #[error("invalid {type_name} for '{param}', was {value}")]
117 InvalidValue {
118 param: String,
120 value: String,
122 type_name: &'static str,
124 },
125 #[error("invalid {type_name} for '{param}' not in range [{min}, {max}], was {value}")]
127 OutOfRange {
128 param: String,
130 min: String,
132 max: String,
134 value: String,
136 type_name: &'static str,
138 },
139 #[error("the '{param}' {collection_kind} `{type_repr}` was not empty")]
141 CollectionNotEmpty {
142 param: String,
144 collection_kind: &'static str,
146 type_repr: String,
148 },
149 #[error("the '{param}' {collection_kind} `{type_repr}` was empty")]
151 CollectionEmpty {
152 param: String,
154 collection_kind: &'static str,
156 type_repr: String,
158 },
159 #[error("the '{key_name}' key {key} was already in the '{map_name}' map `{map_type_repr}`")]
161 KeyPresent {
162 key_name: String,
164 map_name: String,
166 key: String,
168 map_type_repr: String,
170 },
171 #[error("the '{key_name}' key {key} was not in the '{map_name}' map `{map_type_repr}`")]
173 KeyMissing {
174 key_name: String,
176 map_name: String,
178 key: String,
180 map_type_repr: String,
182 },
183 #[error("the '{member_name}' member was already in the '{set_name}' set `{set_type_repr}`")]
185 MemberPresent {
186 member_name: String,
188 set_name: String,
190 set_type_repr: String,
192 },
193 #[error("the '{member_name}' member was not in the '{set_name}' set `{set_type_repr}`")]
195 MemberMissing {
196 member_name: String,
198 set_name: String,
200 set_type_repr: String,
202 },
203}
204
205pub type Result<T> = std::result::Result<T, CorrectnessError>;
207
208pub type CorrectnessResult<T> = Result<T>;
210
211pub trait CorrectnessResultExt<T> {
220 fn expect_display(self, msg: &str) -> T;
223}
224
225impl<T> CorrectnessResultExt<T> for CorrectnessResult<T> {
226 #[inline]
227 #[track_caller]
228 fn expect_display(self, msg: &str) -> T {
229 match self {
230 Ok(value) => value,
231 Err(e) => panic!("{msg}: {e}"),
232 }
233 }
234}
235
236#[inline(always)]
242pub fn check_predicate_true(predicate: bool, fail_msg: &str) -> Result<()> {
243 if !predicate {
244 return Err(CorrectnessError::PredicateViolation {
245 message: fail_msg.to_string(),
246 });
247 }
248 Ok(())
249}
250
251#[inline(always)]
257pub fn check_predicate_false(predicate: bool, fail_msg: &str) -> Result<()> {
258 if predicate {
259 return Err(CorrectnessError::PredicateViolation {
260 message: fail_msg.to_string(),
261 });
262 }
263 Ok(())
264}
265
266#[inline(always)]
275pub fn check_nonempty_string<T: AsRef<str>>(s: T, param: &str) -> Result<()> {
276 if s.as_ref().is_empty() {
277 return Err(CorrectnessError::EmptyString {
278 param: param.to_string(),
279 });
280 }
281 Ok(())
282}
283
284#[inline(always)]
293pub fn check_valid_string_ascii<T: AsRef<str>>(s: T, param: &str) -> Result<()> {
294 let s = s.as_ref();
295
296 if s.is_empty() {
297 return Err(CorrectnessError::EmptyString {
298 param: param.to_string(),
299 });
300 }
301
302 let mut has_non_whitespace = false;
304
305 for c in s.chars() {
306 if !c.is_whitespace() {
307 has_non_whitespace = true;
308 }
309
310 if !c.is_ascii() {
311 return Err(CorrectnessError::NonAsciiString {
312 param: param.to_string(),
313 value: s.to_string(),
314 });
315 }
316 }
317
318 if !has_non_whitespace {
319 return Err(CorrectnessError::WhitespaceString {
320 param: param.to_string(),
321 });
322 }
323
324 Ok(())
325}
326
327#[inline(always)]
338pub fn check_valid_string_utf8<T: AsRef<str>>(s: T, param: &str) -> Result<()> {
339 let s = s.as_ref();
340
341 if s.is_empty() {
342 return Err(CorrectnessError::EmptyString {
343 param: param.to_string(),
344 });
345 }
346
347 let has_non_whitespace = s.chars().any(|c| !c.is_whitespace());
348
349 if !has_non_whitespace {
350 return Err(CorrectnessError::WhitespaceString {
351 param: param.to_string(),
352 });
353 }
354
355 Ok(())
356}
357
358#[inline(always)]
367pub fn check_valid_string_ascii_optional<T: AsRef<str>>(s: Option<T>, param: &str) -> Result<()> {
368 if let Some(s) = s {
369 check_valid_string_ascii(s, param)?;
370 }
371 Ok(())
372}
373
374#[inline(always)]
380pub fn check_string_contains<T: AsRef<str>>(s: T, pat: &str, param: &str) -> Result<()> {
381 let s = s.as_ref();
382 if !s.contains(pat) {
383 return Err(CorrectnessError::MissingSubstring {
384 param: param.to_string(),
385 pattern: pat.to_string(),
386 value: s.to_string(),
387 });
388 }
389 Ok(())
390}
391
392#[inline(always)]
398pub fn check_equal<T: PartialEq + Debug + Display>(
399 lhs: &T,
400 rhs: &T,
401 lhs_param: &str,
402 rhs_param: &str,
403) -> Result<()> {
404 if lhs != rhs {
405 return Err(CorrectnessError::EqualityMismatch {
406 lhs_param: lhs_param.to_string(),
407 rhs_param: rhs_param.to_string(),
408 lhs: lhs.to_string(),
409 rhs: rhs.to_string(),
410 type_name: "value",
411 });
412 }
413 Ok(())
414}
415
416#[inline(always)]
422pub fn check_equal_u8(lhs: u8, rhs: u8, lhs_param: &str, rhs_param: &str) -> Result<()> {
423 if lhs != rhs {
424 return Err(CorrectnessError::EqualityMismatch {
425 lhs_param: lhs_param.to_string(),
426 rhs_param: rhs_param.to_string(),
427 lhs: lhs.to_string(),
428 rhs: rhs.to_string(),
429 type_name: "u8",
430 });
431 }
432 Ok(())
433}
434
435#[inline(always)]
441pub fn check_equal_usize(lhs: usize, rhs: usize, lhs_param: &str, rhs_param: &str) -> Result<()> {
442 if lhs != rhs {
443 return Err(CorrectnessError::EqualityMismatch {
444 lhs_param: lhs_param.to_string(),
445 rhs_param: rhs_param.to_string(),
446 lhs: lhs.to_string(),
447 rhs: rhs.to_string(),
448 type_name: "usize",
449 });
450 }
451 Ok(())
452}
453
454#[inline(always)]
460pub fn check_positive_usize(value: usize, param: &str) -> Result<()> {
461 if value == 0 {
462 return Err(CorrectnessError::NotPositive {
463 param: param.to_string(),
464 value: value.to_string(),
465 type_name: "usize",
466 });
467 }
468 Ok(())
469}
470
471#[inline(always)]
477pub fn check_positive_u64(value: u64, param: &str) -> Result<()> {
478 if value == 0 {
479 return Err(CorrectnessError::NotPositive {
480 param: param.to_string(),
481 value: value.to_string(),
482 type_name: "u64",
483 });
484 }
485 Ok(())
486}
487
488#[inline(always)]
494pub fn check_positive_u128(value: u128, param: &str) -> Result<()> {
495 if value == 0 {
496 return Err(CorrectnessError::NotPositive {
497 param: param.to_string(),
498 value: value.to_string(),
499 type_name: "u128",
500 });
501 }
502 Ok(())
503}
504
505#[inline(always)]
511pub fn check_positive_i64(value: i64, param: &str) -> Result<()> {
512 if value <= 0 {
513 return Err(CorrectnessError::NotPositive {
514 param: param.to_string(),
515 value: value.to_string(),
516 type_name: "i64",
517 });
518 }
519 Ok(())
520}
521
522#[inline(always)]
528pub fn check_positive_i128(value: i128, param: &str) -> Result<()> {
529 if value <= 0 {
530 return Err(CorrectnessError::NotPositive {
531 param: param.to_string(),
532 value: value.to_string(),
533 type_name: "i128",
534 });
535 }
536 Ok(())
537}
538
539#[inline(always)]
545pub fn check_non_negative_f64(value: f64, param: &str) -> Result<()> {
546 if value.is_nan() || value.is_infinite() {
547 return Err(CorrectnessError::InvalidValue {
548 param: param.to_string(),
549 value: value.to_string(),
550 type_name: "f64",
551 });
552 }
553
554 if value < 0.0 {
555 return Err(CorrectnessError::NegativeValue {
556 param: param.to_string(),
557 value: value.to_string(),
558 type_name: "f64",
559 });
560 }
561 Ok(())
562}
563
564#[inline(always)]
570pub fn check_in_range_inclusive_u8(value: u8, l: u8, r: u8, param: &str) -> Result<()> {
571 if value < l || value > r {
572 return Err(CorrectnessError::OutOfRange {
573 param: param.to_string(),
574 min: l.to_string(),
575 max: r.to_string(),
576 value: value.to_string(),
577 type_name: "u8",
578 });
579 }
580 Ok(())
581}
582
583#[inline(always)]
589pub fn check_in_range_inclusive_u64(value: u64, l: u64, r: u64, param: &str) -> Result<()> {
590 if value < l || value > r {
591 return Err(CorrectnessError::OutOfRange {
592 param: param.to_string(),
593 min: l.to_string(),
594 max: r.to_string(),
595 value: value.to_string(),
596 type_name: "u64",
597 });
598 }
599 Ok(())
600}
601
602#[inline(always)]
608pub fn check_in_range_inclusive_i64(value: i64, l: i64, r: i64, param: &str) -> Result<()> {
609 if value < l || value > r {
610 return Err(CorrectnessError::OutOfRange {
611 param: param.to_string(),
612 min: l.to_string(),
613 max: r.to_string(),
614 value: value.to_string(),
615 type_name: "i64",
616 });
617 }
618 Ok(())
619}
620
621#[inline(always)]
627pub fn check_in_range_inclusive_f64(value: f64, l: f64, r: f64, param: &str) -> Result<()> {
628 const EPSILON: f64 = 1e-15;
634
635 if value.is_nan() || value.is_infinite() {
636 return Err(CorrectnessError::InvalidValue {
637 param: param.to_string(),
638 value: value.to_string(),
639 type_name: "f64",
640 });
641 }
642
643 if value < l - EPSILON || value > r + EPSILON {
644 return Err(CorrectnessError::OutOfRange {
645 param: param.to_string(),
646 min: l.to_string(),
647 max: r.to_string(),
648 value: value.to_string(),
649 type_name: "f64",
650 });
651 }
652 Ok(())
653}
654
655#[inline(always)]
661pub fn check_in_range_inclusive_usize(value: usize, l: usize, r: usize, param: &str) -> Result<()> {
662 if value < l || value > r {
663 return Err(CorrectnessError::OutOfRange {
664 param: param.to_string(),
665 min: l.to_string(),
666 max: r.to_string(),
667 value: value.to_string(),
668 type_name: "usize",
669 });
670 }
671 Ok(())
672}
673
674#[inline(always)]
680pub fn check_slice_empty<T>(slice: &[T], param: &str) -> Result<()> {
681 if !slice.is_empty() {
682 return Err(CorrectnessError::CollectionNotEmpty {
683 param: param.to_string(),
684 collection_kind: "slice",
685 type_repr: slice_type_repr::<T>(),
686 });
687 }
688 Ok(())
689}
690
691#[inline(always)]
697pub fn check_slice_not_empty<T>(slice: &[T], param: &str) -> Result<()> {
698 if slice.is_empty() {
699 return Err(CorrectnessError::CollectionEmpty {
700 param: param.to_string(),
701 collection_kind: "slice",
702 type_repr: slice_type_repr::<T>(),
703 });
704 }
705 Ok(())
706}
707
708#[inline(always)]
714pub fn check_map_empty<M>(map: &M, param: &str) -> Result<()>
715where
716 M: MapLike,
717{
718 if !map.is_empty() {
719 return Err(CorrectnessError::CollectionNotEmpty {
720 param: param.to_string(),
721 collection_kind: "map",
722 type_repr: map_type_repr::<M>(),
723 });
724 }
725 Ok(())
726}
727
728#[inline(always)]
734pub fn check_map_not_empty<M>(map: &M, param: &str) -> Result<()>
735where
736 M: MapLike,
737{
738 if map.is_empty() {
739 return Err(CorrectnessError::CollectionEmpty {
740 param: param.to_string(),
741 collection_kind: "map",
742 type_repr: map_type_repr::<M>(),
743 });
744 }
745 Ok(())
746}
747
748#[inline(always)]
754pub fn check_key_not_in_map<M>(key: &M::Key, map: &M, key_name: &str, map_name: &str) -> Result<()>
755where
756 M: MapLike,
757{
758 if map.contains_key(key) {
759 return Err(CorrectnessError::KeyPresent {
760 key_name: key_name.to_string(),
761 map_name: map_name.to_string(),
762 key: key.to_string(),
763 map_type_repr: map_type_repr::<M>(),
764 });
765 }
766 Ok(())
767}
768
769#[inline(always)]
775pub fn check_key_in_map<M>(key: &M::Key, map: &M, key_name: &str, map_name: &str) -> Result<()>
776where
777 M: MapLike,
778{
779 if !map.contains_key(key) {
780 return Err(CorrectnessError::KeyMissing {
781 key_name: key_name.to_string(),
782 map_name: map_name.to_string(),
783 key: key.to_string(),
784 map_type_repr: map_type_repr::<M>(),
785 });
786 }
787 Ok(())
788}
789
790#[inline(always)]
796pub fn check_member_not_in_set<S>(
797 member: &S::Item,
798 set: &S,
799 member_name: &str,
800 set_name: &str,
801) -> Result<()>
802where
803 S: SetLike,
804{
805 if set.contains(member) {
806 return Err(CorrectnessError::MemberPresent {
807 member_name: member_name.to_string(),
808 set_name: set_name.to_string(),
809 set_type_repr: set_type_repr::<S>(),
810 });
811 }
812 Ok(())
813}
814
815#[inline(always)]
821pub fn check_member_in_set<S>(
822 member: &S::Item,
823 set: &S,
824 member_name: &str,
825 set_name: &str,
826) -> Result<()>
827where
828 S: SetLike,
829{
830 if !set.contains(member) {
831 return Err(CorrectnessError::MemberMissing {
832 member_name: member_name.to_string(),
833 set_name: set_name.to_string(),
834 set_type_repr: set_type_repr::<S>(),
835 });
836 }
837 Ok(())
838}
839
840#[inline(always)]
846pub fn check_positive_decimal(value: Decimal, param: &str) -> Result<()> {
847 if value <= Decimal::ZERO {
848 return Err(CorrectnessError::NotPositive {
849 param: param.to_string(),
850 value: value.to_string(),
851 type_name: "Decimal",
852 });
853 }
854 Ok(())
855}
856
857fn slice_type_repr<T>() -> String {
858 format!("&[{}]", std::any::type_name::<T>())
859}
860
861fn map_type_repr<M>() -> String
862where
863 M: MapLike,
864{
865 format!(
866 "&<{}, {}>",
867 std::any::type_name::<M::Key>(),
868 std::any::type_name::<M::Value>(),
869 )
870}
871
872fn set_type_repr<S>() -> String
873where
874 S: SetLike,
875{
876 format!("&<{}>", std::any::type_name::<S::Item>())
877}
878
879#[cfg(test)]
880mod tests {
881 use std::{
882 collections::{HashMap, HashSet},
883 fmt::Display,
884 str::FromStr,
885 };
886
887 use rstest::rstest;
888 use rust_decimal::Decimal;
889
890 use super::*;
891
892 #[rstest]
893 fn test_check_predicate_true_returns_typed_error_with_stable_display() {
894 let error = check_predicate_true(false, "the predicate was false").unwrap_err();
895
896 assert_eq!(
897 error,
898 CorrectnessError::PredicateViolation {
899 message: "the predicate was false".to_string(),
900 }
901 );
902 assert_eq!(error.to_string(), "the predicate was false");
903 }
904
905 #[rstest]
906 fn test_expect_display_returns_ok_value() {
907 let result: CorrectnessResult<i32> = Ok(42);
908 assert_eq!(result.expect_display(FAILED), 42);
909 }
910
911 #[rstest]
912 #[should_panic(expected = "Condition failed: invalid string for 'value', was empty")]
913 fn test_expect_display_panics_with_display_form_on_err() {
914 let result: CorrectnessResult<()> = Err(CorrectnessError::EmptyString {
915 param: "value".to_string(),
916 });
917 result.expect_display(FAILED);
918 }
919
920 #[rstest]
921 #[should_panic(expected = "custom prefix: the predicate was false")]
922 fn test_expect_display_uses_provided_prefix() {
923 let result: CorrectnessResult<()> = Err(CorrectnessError::PredicateViolation {
924 message: "the predicate was false".to_string(),
925 });
926 result.expect_display("custom prefix");
927 }
928
929 #[rstest]
930 #[case(false, false)]
931 #[case(true, true)]
932 fn test_check_predicate_true(#[case] predicate: bool, #[case] expected: bool) {
933 let result = check_predicate_true(predicate, "the predicate was false").is_ok();
934 assert_eq!(result, expected);
935 }
936
937 #[rstest]
938 #[case(false, true)]
939 #[case(true, false)]
940 fn test_check_predicate_false(#[case] predicate: bool, #[case] expected: bool) {
941 let result = check_predicate_false(predicate, "the predicate was true").is_ok();
942 assert_eq!(result, expected);
943 }
944
945 #[rstest]
946 #[case("a")]
947 #[case(" ")] #[case(" ")] #[case("🦀")] #[case(" a")]
951 #[case("a ")]
952 #[case("abc")]
953 fn test_check_nonempty_string_with_valid_values(#[case] s: &str) {
954 assert!(check_nonempty_string(s, "value").is_ok());
955 }
956
957 #[rstest]
958 #[case("")] fn test_check_nonempty_string_with_invalid_values(#[case] s: &str) {
960 assert!(check_nonempty_string(s, "value").is_err());
961 }
962
963 #[rstest]
964 #[case(" a")]
965 #[case("a ")]
966 #[case("a a")]
967 #[case(" a ")]
968 #[case("abc")]
969 fn test_check_valid_string_ascii_with_valid_value(#[case] s: &str) {
970 assert!(check_valid_string_ascii(s, "value").is_ok());
971 }
972
973 #[rstest]
974 #[case("")] #[case(" ")] #[case(" ")] #[case("🦀")] fn test_check_valid_string_ascii_with_invalid_values(#[case] s: &str) {
979 assert!(check_valid_string_ascii(s, "value").is_err());
980 }
981
982 #[rstest]
983 fn test_check_valid_string_ascii_returns_empty_string_error_with_stable_display() {
984 let error = check_valid_string_ascii("", "value").unwrap_err();
985
986 assert_eq!(
987 error,
988 CorrectnessError::EmptyString {
989 param: "value".to_string(),
990 }
991 );
992 assert_eq!(error.to_string(), "invalid string for 'value', was empty");
993 }
994
995 #[rstest]
996 fn test_check_valid_string_ascii_returns_non_ascii_error_with_stable_display() {
997 let error = check_valid_string_ascii("🦀", "value").unwrap_err();
998
999 assert_eq!(
1000 error,
1001 CorrectnessError::NonAsciiString {
1002 param: "value".to_string(),
1003 value: "🦀".to_string(),
1004 }
1005 );
1006 assert_eq!(
1007 error.to_string(),
1008 "invalid string for 'value' contained a non-ASCII char, was '🦀'"
1009 );
1010 }
1011
1012 #[rstest]
1013 fn test_check_valid_string_ascii_returns_whitespace_string_error_with_stable_display() {
1014 let error = check_valid_string_ascii(" ", "value").unwrap_err();
1015
1016 assert_eq!(
1017 error,
1018 CorrectnessError::WhitespaceString {
1019 param: "value".to_string(),
1020 }
1021 );
1022 assert_eq!(
1023 error.to_string(),
1024 "invalid string for 'value', was all whitespace"
1025 );
1026 }
1027
1028 #[rstest]
1029 #[case(" a")]
1030 #[case("a ")]
1031 #[case("abc")]
1032 #[case("ETHUSDT")]
1033 fn test_check_valid_string_utf8_with_valid_values(#[case] s: &str) {
1034 assert!(check_valid_string_utf8(s, "value").is_ok());
1035 }
1036
1037 #[rstest]
1038 #[case("")] #[case(" ")] #[case(" ")] fn test_check_valid_string_utf8_with_invalid_values(#[case] s: &str) {
1042 assert!(check_valid_string_utf8(s, "value").is_err());
1043 }
1044
1045 #[rstest]
1046 #[case(None)]
1047 #[case(Some(" a"))]
1048 #[case(Some("a "))]
1049 #[case(Some("a a"))]
1050 #[case(Some(" a "))]
1051 #[case(Some("abc"))]
1052 fn test_check_valid_string_ascii_optional_with_valid_value(#[case] s: Option<&str>) {
1053 assert!(check_valid_string_ascii_optional(s, "value").is_ok());
1054 }
1055
1056 #[rstest]
1057 #[case("a", "a")]
1058 fn test_check_string_contains_when_does_contain(#[case] s: &str, #[case] pat: &str) {
1059 assert!(check_string_contains(s, pat, "value").is_ok());
1060 }
1061
1062 #[rstest]
1063 #[case("a", "b")]
1064 fn test_check_string_contains_when_does_not_contain(#[case] s: &str, #[case] pat: &str) {
1065 assert!(check_string_contains(s, pat, "value").is_err());
1066 }
1067
1068 #[rstest]
1069 #[case(0u8, 0u8, "left", "right", true)]
1070 #[case(1u8, 1u8, "left", "right", true)]
1071 #[case(0u8, 1u8, "left", "right", false)]
1072 #[case(1u8, 0u8, "left", "right", false)]
1073 #[case(10i32, 10i32, "left", "right", true)]
1074 #[case(10i32, 20i32, "left", "right", false)]
1075 #[case("hello", "hello", "left", "right", true)]
1076 #[case("hello", "world", "left", "right", false)]
1077 fn test_check_equal<T: PartialEq + Debug + Display>(
1078 #[case] lhs: T,
1079 #[case] rhs: T,
1080 #[case] lhs_param: &str,
1081 #[case] rhs_param: &str,
1082 #[case] expected: bool,
1083 ) {
1084 let result = check_equal(&lhs, &rhs, lhs_param, rhs_param).is_ok();
1085 assert_eq!(result, expected);
1086 }
1087
1088 #[rstest]
1089 #[case(0, 0, "left", "right", true)]
1090 #[case(1, 1, "left", "right", true)]
1091 #[case(0, 1, "left", "right", false)]
1092 #[case(1, 0, "left", "right", false)]
1093 fn test_check_equal_u8_when_equal(
1094 #[case] lhs: u8,
1095 #[case] rhs: u8,
1096 #[case] lhs_param: &str,
1097 #[case] rhs_param: &str,
1098 #[case] expected: bool,
1099 ) {
1100 let result = check_equal_u8(lhs, rhs, lhs_param, rhs_param).is_ok();
1101 assert_eq!(result, expected);
1102 }
1103
1104 #[rstest]
1105 fn test_check_equal_u8_returns_equality_mismatch_with_stable_display() {
1106 let error = check_equal_u8(1, 2, "left", "right").unwrap_err();
1107
1108 assert_eq!(
1109 error,
1110 CorrectnessError::EqualityMismatch {
1111 lhs_param: "left".to_string(),
1112 rhs_param: "right".to_string(),
1113 lhs: "1".to_string(),
1114 rhs: "2".to_string(),
1115 type_name: "u8",
1116 }
1117 );
1118 assert_eq!(
1119 error.to_string(),
1120 "'left' u8 of 1 was not equal to 'right' u8 of 2"
1121 );
1122 }
1123
1124 #[rstest]
1125 #[case(0, 0, "left", "right", true)]
1126 #[case(1, 1, "left", "right", true)]
1127 #[case(0, 1, "left", "right", false)]
1128 #[case(1, 0, "left", "right", false)]
1129 fn test_check_equal_usize_when_equal(
1130 #[case] lhs: usize,
1131 #[case] rhs: usize,
1132 #[case] lhs_param: &str,
1133 #[case] rhs_param: &str,
1134 #[case] expected: bool,
1135 ) {
1136 let result = check_equal_usize(lhs, rhs, lhs_param, rhs_param).is_ok();
1137 assert_eq!(result, expected);
1138 }
1139
1140 #[rstest]
1141 #[case(1, true)]
1142 #[case(usize::MAX, true)]
1143 #[case(0, false)]
1144 fn test_check_positive_usize(#[case] value: usize, #[case] expected: bool) {
1145 assert_eq!(check_positive_usize(value, "value").is_ok(), expected);
1146 }
1147
1148 #[rstest]
1149 fn test_check_positive_usize_returns_not_positive_error_with_stable_display() {
1150 let error = check_positive_usize(0, "param").unwrap_err();
1151
1152 assert_eq!(
1153 error,
1154 CorrectnessError::NotPositive {
1155 param: "param".to_string(),
1156 value: "0".to_string(),
1157 type_name: "usize",
1158 }
1159 );
1160 assert_eq!(
1161 error.to_string(),
1162 "invalid usize for 'param' not positive, was 0"
1163 );
1164 }
1165
1166 #[rstest]
1167 #[case(1, "value")]
1168 fn test_check_positive_u64_when_positive(#[case] value: u64, #[case] param: &str) {
1169 assert!(check_positive_u64(value, param).is_ok());
1170 }
1171
1172 #[rstest]
1173 #[case(0, "value")]
1174 fn test_check_positive_u64_when_not_positive(#[case] value: u64, #[case] param: &str) {
1175 assert!(check_positive_u64(value, param).is_err());
1176 }
1177
1178 #[rstest]
1179 #[case(1, "value")]
1180 fn test_check_positive_i64_when_positive(#[case] value: i64, #[case] param: &str) {
1181 assert!(check_positive_i64(value, param).is_ok());
1182 }
1183
1184 #[rstest]
1185 #[case(0, "value")]
1186 #[case(-1, "value")]
1187 fn test_check_positive_i64_when_not_positive(#[case] value: i64, #[case] param: &str) {
1188 assert!(check_positive_i64(value, param).is_err());
1189 }
1190
1191 #[rstest]
1192 #[case(0.0, "value")]
1193 #[case(1.0, "value")]
1194 fn test_check_non_negative_f64_when_not_negative(#[case] value: f64, #[case] param: &str) {
1195 assert!(check_non_negative_f64(value, param).is_ok());
1196 }
1197
1198 #[rstest]
1199 #[case(f64::NAN, "value")]
1200 #[case(f64::INFINITY, "value")]
1201 #[case(f64::NEG_INFINITY, "value")]
1202 #[case(-0.1, "value")]
1203 fn test_check_non_negative_f64_when_negative(#[case] value: f64, #[case] param: &str) {
1204 assert!(check_non_negative_f64(value, param).is_err());
1205 }
1206
1207 #[rstest]
1208 #[case(0, 0, 0, "value")]
1209 #[case(0, 0, 1, "value")]
1210 #[case(1, 0, 1, "value")]
1211 fn test_check_in_range_inclusive_u8_when_in_range(
1212 #[case] value: u8,
1213 #[case] l: u8,
1214 #[case] r: u8,
1215 #[case] desc: &str,
1216 ) {
1217 assert!(check_in_range_inclusive_u8(value, l, r, desc).is_ok());
1218 }
1219
1220 #[rstest]
1221 #[case(0, 1, 2, "value")]
1222 #[case(3, 1, 2, "value")]
1223 fn test_check_in_range_inclusive_u8_when_out_of_range(
1224 #[case] value: u8,
1225 #[case] l: u8,
1226 #[case] r: u8,
1227 #[case] param: &str,
1228 ) {
1229 assert!(check_in_range_inclusive_u8(value, l, r, param).is_err());
1230 }
1231
1232 #[rstest]
1233 #[case(0, 0, 0, "value")]
1234 #[case(0, 0, 1, "value")]
1235 #[case(1, 0, 1, "value")]
1236 fn test_check_in_range_inclusive_u64_when_in_range(
1237 #[case] value: u64,
1238 #[case] l: u64,
1239 #[case] r: u64,
1240 #[case] param: &str,
1241 ) {
1242 assert!(check_in_range_inclusive_u64(value, l, r, param).is_ok());
1243 }
1244
1245 #[rstest]
1246 #[case(0, 1, 2, "value")]
1247 #[case(3, 1, 2, "value")]
1248 fn test_check_in_range_inclusive_u64_when_out_of_range(
1249 #[case] value: u64,
1250 #[case] l: u64,
1251 #[case] r: u64,
1252 #[case] param: &str,
1253 ) {
1254 assert!(check_in_range_inclusive_u64(value, l, r, param).is_err());
1255 }
1256
1257 #[rstest]
1258 #[case(0, 0, 0, "value")]
1259 #[case(0, 0, 1, "value")]
1260 #[case(1, 0, 1, "value")]
1261 fn test_check_in_range_inclusive_i64_when_in_range(
1262 #[case] value: i64,
1263 #[case] l: i64,
1264 #[case] r: i64,
1265 #[case] param: &str,
1266 ) {
1267 assert!(check_in_range_inclusive_i64(value, l, r, param).is_ok());
1268 }
1269
1270 #[rstest]
1271 #[case(0.0, 0.0, 0.0, "value")]
1272 #[case(0.0, 0.0, 1.0, "value")]
1273 #[case(1.0, 0.0, 1.0, "value")]
1274 fn test_check_in_range_inclusive_f64_when_in_range(
1275 #[case] value: f64,
1276 #[case] l: f64,
1277 #[case] r: f64,
1278 #[case] param: &str,
1279 ) {
1280 assert!(check_in_range_inclusive_f64(value, l, r, param).is_ok());
1281 }
1282
1283 #[rstest]
1284 #[case(-1e16, 0.0, 0.0, "value")]
1285 #[case(1.0 + 1e16, 0.0, 1.0, "value")]
1286 fn test_check_in_range_inclusive_f64_when_out_of_range(
1287 #[case] value: f64,
1288 #[case] l: f64,
1289 #[case] r: f64,
1290 #[case] param: &str,
1291 ) {
1292 assert!(check_in_range_inclusive_f64(value, l, r, param).is_err());
1293 }
1294
1295 #[rstest]
1296 #[case(0, 1, 2, "value")]
1297 #[case(3, 1, 2, "value")]
1298 fn test_check_in_range_inclusive_i64_when_out_of_range(
1299 #[case] value: i64,
1300 #[case] l: i64,
1301 #[case] r: i64,
1302 #[case] param: &str,
1303 ) {
1304 assert!(check_in_range_inclusive_i64(value, l, r, param).is_err());
1305 }
1306
1307 #[rstest]
1308 #[case(0, 0, 0, "value")]
1309 #[case(0, 0, 1, "value")]
1310 #[case(1, 0, 1, "value")]
1311 fn test_check_in_range_inclusive_usize_when_in_range(
1312 #[case] value: usize,
1313 #[case] l: usize,
1314 #[case] r: usize,
1315 #[case] param: &str,
1316 ) {
1317 assert!(check_in_range_inclusive_usize(value, l, r, param).is_ok());
1318 }
1319
1320 #[rstest]
1321 #[case(0, 1, 2, "value")]
1322 #[case(3, 1, 2, "value")]
1323 fn test_check_in_range_inclusive_usize_when_out_of_range(
1324 #[case] value: usize,
1325 #[case] l: usize,
1326 #[case] r: usize,
1327 #[case] param: &str,
1328 ) {
1329 assert!(check_in_range_inclusive_usize(value, l, r, param).is_err());
1330 }
1331
1332 #[rstest]
1333 fn test_check_in_range_inclusive_usize_returns_out_of_range_error_with_stable_display() {
1334 let error = check_in_range_inclusive_usize(3, 1, 2, "value").unwrap_err();
1335
1336 assert_eq!(
1337 error,
1338 CorrectnessError::OutOfRange {
1339 param: "value".to_string(),
1340 min: "1".to_string(),
1341 max: "2".to_string(),
1342 value: "3".to_string(),
1343 type_name: "usize",
1344 }
1345 );
1346 assert_eq!(
1347 error.to_string(),
1348 "invalid usize for 'value' not in range [1, 2], was 3"
1349 );
1350 }
1351
1352 #[rstest]
1353 #[case(vec![], true)]
1354 #[case(vec![1_u8], false)]
1355 fn test_check_slice_empty(#[case] collection: Vec<u8>, #[case] expected: bool) {
1356 let result = check_slice_empty(collection.as_slice(), "param").is_ok();
1357 assert_eq!(result, expected);
1358 }
1359
1360 #[rstest]
1361 #[case(vec![], false)]
1362 #[case(vec![1_u8], true)]
1363 fn test_check_slice_not_empty(#[case] collection: Vec<u8>, #[case] expected: bool) {
1364 let result = check_slice_not_empty(collection.as_slice(), "param").is_ok();
1365 assert_eq!(result, expected);
1366 }
1367
1368 #[rstest]
1369 fn test_check_slice_not_empty_returns_collection_empty_error_with_stable_display() {
1370 let error = check_slice_not_empty::<u8>(&[], "param").unwrap_err();
1371
1372 assert_eq!(
1373 error,
1374 CorrectnessError::CollectionEmpty {
1375 param: "param".to_string(),
1376 collection_kind: "slice",
1377 type_repr: "&[u8]".to_string(),
1378 }
1379 );
1380 assert_eq!(error.to_string(), "the 'param' slice `&[u8]` was empty");
1381 }
1382
1383 #[rstest]
1384 #[case(HashMap::new(), true)]
1385 #[case(HashMap::from([("A".to_string(), 1_u8)]), false)]
1386 fn test_check_map_empty(#[case] map: HashMap<String, u8>, #[case] expected: bool) {
1387 let result = check_map_empty(&map, "param").is_ok();
1388 assert_eq!(result, expected);
1389 }
1390
1391 #[rstest]
1392 #[case(HashMap::new(), false)]
1393 #[case(HashMap::from([("A".to_string(), 1_u8)]), true)]
1394 fn test_check_map_not_empty(#[case] map: HashMap<String, u8>, #[case] expected: bool) {
1395 let result = check_map_not_empty(&map, "param").is_ok();
1396 assert_eq!(result, expected);
1397 }
1398
1399 #[rstest]
1400 #[case(&HashMap::<u32, u32>::new(), 5, "key", "map", true)] #[case(&HashMap::from([(1, 10), (2, 20)]), 1, "key", "map", false)] #[case(&HashMap::from([(1, 10), (2, 20)]), 5, "key", "map", true)] fn test_check_key_not_in_map(
1404 #[case] map: &HashMap<u32, u32>,
1405 #[case] key: u32,
1406 #[case] key_name: &str,
1407 #[case] map_name: &str,
1408 #[case] expected: bool,
1409 ) {
1410 let result = check_key_not_in_map(&key, map, key_name, map_name).is_ok();
1411 assert_eq!(result, expected);
1412 }
1413
1414 #[rstest]
1415 #[case(&HashMap::<u32, u32>::new(), 5, "key", "map", false)] #[case(&HashMap::from([(1, 10), (2, 20)]), 1, "key", "map", true)] #[case(&HashMap::from([(1, 10), (2, 20)]), 5, "key", "map", false)] fn test_check_key_in_map(
1419 #[case] map: &HashMap<u32, u32>,
1420 #[case] key: u32,
1421 #[case] key_name: &str,
1422 #[case] map_name: &str,
1423 #[case] expected: bool,
1424 ) {
1425 let result = check_key_in_map(&key, map, key_name, map_name).is_ok();
1426 assert_eq!(result, expected);
1427 }
1428
1429 #[rstest]
1430 fn test_check_key_in_map_returns_key_missing_error_with_stable_display() {
1431 let map = HashMap::<u32, u32>::new();
1432 let error = check_key_in_map(&5, &map, "key", "map").unwrap_err();
1433
1434 assert_eq!(
1435 error,
1436 CorrectnessError::KeyMissing {
1437 key_name: "key".to_string(),
1438 map_name: "map".to_string(),
1439 key: "5".to_string(),
1440 map_type_repr: "&<u32, u32>".to_string(),
1441 }
1442 );
1443 assert_eq!(
1444 error.to_string(),
1445 "the 'key' key 5 was not in the 'map' map `&<u32, u32>`"
1446 );
1447 }
1448
1449 #[rstest]
1450 #[case(&HashSet::<u32>::new(), 5, "member", "set", true)] #[case(&HashSet::from([1, 2]), 1, "member", "set", false)] #[case(&HashSet::from([1, 2]), 5, "member", "set", true)] fn test_check_member_not_in_set(
1454 #[case] set: &HashSet<u32>,
1455 #[case] member: u32,
1456 #[case] member_name: &str,
1457 #[case] set_name: &str,
1458 #[case] expected: bool,
1459 ) {
1460 let result = check_member_not_in_set(&member, set, member_name, set_name).is_ok();
1461 assert_eq!(result, expected);
1462 }
1463
1464 #[rstest]
1465 #[case(&HashSet::<u32>::new(), 5, "member", "set", false)] #[case(&HashSet::from([1, 2]), 1, "member", "set", true)] #[case(&HashSet::from([1, 2]), 5, "member", "set", false)] fn test_check_member_in_set(
1469 #[case] set: &HashSet<u32>,
1470 #[case] member: u32,
1471 #[case] member_name: &str,
1472 #[case] set_name: &str,
1473 #[case] expected: bool,
1474 ) {
1475 let result = check_member_in_set(&member, set, member_name, set_name).is_ok();
1476 assert_eq!(result, expected);
1477 }
1478
1479 #[rstest]
1480 #[case("1", true)] #[case("0.0000000000000000000000000001", true)] #[case("79228162514264337593543950335", true)] #[case("0", false)] #[case("-0.0000000000000000000000000001", false)] #[case("-1", false)] fn test_check_positive_decimal(#[case] raw: &str, #[case] expected: bool) {
1487 let value = Decimal::from_str(raw).expect("valid decimal literal");
1488 let result = super::check_positive_decimal(value, "param").is_ok();
1489 assert_eq!(result, expected);
1490 }
1491
1492 #[rstest]
1493 #[case(1, true)]
1494 #[case(u128::MAX, true)]
1495 #[case(0, false)]
1496 fn test_check_positive_u128(#[case] value: u128, #[case] expected: bool) {
1497 assert_eq!(check_positive_u128(value, "value").is_ok(), expected);
1498 }
1499
1500 #[rstest]
1501 #[case(1, true)]
1502 #[case(i128::MAX, true)]
1503 #[case(0, false)]
1504 #[case(-1, false)]
1505 #[case(i128::MIN, false)]
1506 fn test_check_positive_i128(#[case] value: i128, #[case] expected: bool) {
1507 assert_eq!(check_positive_i128(value, "value").is_ok(), expected);
1508 }
1509
1510 #[rstest]
1511 fn test_check_positive_decimal_returns_not_positive_error_with_stable_display() {
1512 let error = check_positive_decimal(Decimal::ZERO, "param").unwrap_err();
1513
1514 assert_eq!(
1515 error,
1516 CorrectnessError::NotPositive {
1517 param: "param".to_string(),
1518 value: "0".to_string(),
1519 type_name: "Decimal",
1520 }
1521 );
1522 assert_eq!(
1523 error.to_string(),
1524 "invalid Decimal for 'param' not positive, was 0"
1525 );
1526 }
1527}