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_u64(value: u64, 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: "u64",
466 });
467 }
468 Ok(())
469}
470
471#[inline(always)]
477pub fn check_positive_u128(value: u128, 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: "u128",
483 });
484 }
485 Ok(())
486}
487
488#[inline(always)]
494pub fn check_positive_i64(value: i64, 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: "i64",
500 });
501 }
502 Ok(())
503}
504
505#[inline(always)]
511pub fn check_positive_i128(value: i128, 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: "i128",
517 });
518 }
519 Ok(())
520}
521
522#[inline(always)]
528pub fn check_non_negative_f64(value: f64, param: &str) -> Result<()> {
529 if value.is_nan() || value.is_infinite() {
530 return Err(CorrectnessError::InvalidValue {
531 param: param.to_string(),
532 value: value.to_string(),
533 type_name: "f64",
534 });
535 }
536
537 if value < 0.0 {
538 return Err(CorrectnessError::NegativeValue {
539 param: param.to_string(),
540 value: value.to_string(),
541 type_name: "f64",
542 });
543 }
544 Ok(())
545}
546
547#[inline(always)]
553pub fn check_in_range_inclusive_u8(value: u8, l: u8, r: u8, param: &str) -> Result<()> {
554 if value < l || value > r {
555 return Err(CorrectnessError::OutOfRange {
556 param: param.to_string(),
557 min: l.to_string(),
558 max: r.to_string(),
559 value: value.to_string(),
560 type_name: "u8",
561 });
562 }
563 Ok(())
564}
565
566#[inline(always)]
572pub fn check_in_range_inclusive_u64(value: u64, l: u64, r: u64, param: &str) -> Result<()> {
573 if value < l || value > r {
574 return Err(CorrectnessError::OutOfRange {
575 param: param.to_string(),
576 min: l.to_string(),
577 max: r.to_string(),
578 value: value.to_string(),
579 type_name: "u64",
580 });
581 }
582 Ok(())
583}
584
585#[inline(always)]
591pub fn check_in_range_inclusive_i64(value: i64, l: i64, r: i64, param: &str) -> Result<()> {
592 if value < l || value > r {
593 return Err(CorrectnessError::OutOfRange {
594 param: param.to_string(),
595 min: l.to_string(),
596 max: r.to_string(),
597 value: value.to_string(),
598 type_name: "i64",
599 });
600 }
601 Ok(())
602}
603
604#[inline(always)]
610pub fn check_in_range_inclusive_f64(value: f64, l: f64, r: f64, param: &str) -> Result<()> {
611 const EPSILON: f64 = 1e-15;
617
618 if value.is_nan() || value.is_infinite() {
619 return Err(CorrectnessError::InvalidValue {
620 param: param.to_string(),
621 value: value.to_string(),
622 type_name: "f64",
623 });
624 }
625
626 if value < l - EPSILON || value > r + EPSILON {
627 return Err(CorrectnessError::OutOfRange {
628 param: param.to_string(),
629 min: l.to_string(),
630 max: r.to_string(),
631 value: value.to_string(),
632 type_name: "f64",
633 });
634 }
635 Ok(())
636}
637
638#[inline(always)]
644pub fn check_in_range_inclusive_usize(value: usize, l: usize, r: usize, param: &str) -> Result<()> {
645 if value < l || value > r {
646 return Err(CorrectnessError::OutOfRange {
647 param: param.to_string(),
648 min: l.to_string(),
649 max: r.to_string(),
650 value: value.to_string(),
651 type_name: "usize",
652 });
653 }
654 Ok(())
655}
656
657#[inline(always)]
663pub fn check_slice_empty<T>(slice: &[T], param: &str) -> Result<()> {
664 if !slice.is_empty() {
665 return Err(CorrectnessError::CollectionNotEmpty {
666 param: param.to_string(),
667 collection_kind: "slice",
668 type_repr: slice_type_repr::<T>(),
669 });
670 }
671 Ok(())
672}
673
674#[inline(always)]
680pub fn check_slice_not_empty<T>(slice: &[T], param: &str) -> Result<()> {
681 if slice.is_empty() {
682 return Err(CorrectnessError::CollectionEmpty {
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_map_empty<M>(map: &M, param: &str) -> Result<()>
698where
699 M: MapLike,
700{
701 if !map.is_empty() {
702 return Err(CorrectnessError::CollectionNotEmpty {
703 param: param.to_string(),
704 collection_kind: "map",
705 type_repr: map_type_repr::<M>(),
706 });
707 }
708 Ok(())
709}
710
711#[inline(always)]
717pub fn check_map_not_empty<M>(map: &M, param: &str) -> Result<()>
718where
719 M: MapLike,
720{
721 if map.is_empty() {
722 return Err(CorrectnessError::CollectionEmpty {
723 param: param.to_string(),
724 collection_kind: "map",
725 type_repr: map_type_repr::<M>(),
726 });
727 }
728 Ok(())
729}
730
731#[inline(always)]
737pub fn check_key_not_in_map<M>(key: &M::Key, map: &M, key_name: &str, map_name: &str) -> Result<()>
738where
739 M: MapLike,
740{
741 if map.contains_key(key) {
742 return Err(CorrectnessError::KeyPresent {
743 key_name: key_name.to_string(),
744 map_name: map_name.to_string(),
745 key: key.to_string(),
746 map_type_repr: map_type_repr::<M>(),
747 });
748 }
749 Ok(())
750}
751
752#[inline(always)]
758pub fn check_key_in_map<M>(key: &M::Key, map: &M, key_name: &str, map_name: &str) -> Result<()>
759where
760 M: MapLike,
761{
762 if !map.contains_key(key) {
763 return Err(CorrectnessError::KeyMissing {
764 key_name: key_name.to_string(),
765 map_name: map_name.to_string(),
766 key: key.to_string(),
767 map_type_repr: map_type_repr::<M>(),
768 });
769 }
770 Ok(())
771}
772
773#[inline(always)]
779pub fn check_member_not_in_set<S>(
780 member: &S::Item,
781 set: &S,
782 member_name: &str,
783 set_name: &str,
784) -> Result<()>
785where
786 S: SetLike,
787{
788 if set.contains(member) {
789 return Err(CorrectnessError::MemberPresent {
790 member_name: member_name.to_string(),
791 set_name: set_name.to_string(),
792 set_type_repr: set_type_repr::<S>(),
793 });
794 }
795 Ok(())
796}
797
798#[inline(always)]
804pub fn check_member_in_set<S>(
805 member: &S::Item,
806 set: &S,
807 member_name: &str,
808 set_name: &str,
809) -> Result<()>
810where
811 S: SetLike,
812{
813 if !set.contains(member) {
814 return Err(CorrectnessError::MemberMissing {
815 member_name: member_name.to_string(),
816 set_name: set_name.to_string(),
817 set_type_repr: set_type_repr::<S>(),
818 });
819 }
820 Ok(())
821}
822
823#[inline(always)]
829pub fn check_positive_decimal(value: Decimal, param: &str) -> Result<()> {
830 if value <= Decimal::ZERO {
831 return Err(CorrectnessError::NotPositive {
832 param: param.to_string(),
833 value: value.to_string(),
834 type_name: "Decimal",
835 });
836 }
837 Ok(())
838}
839
840fn slice_type_repr<T>() -> String {
841 format!("&[{}]", std::any::type_name::<T>())
842}
843
844fn map_type_repr<M>() -> String
845where
846 M: MapLike,
847{
848 format!(
849 "&<{}, {}>",
850 std::any::type_name::<M::Key>(),
851 std::any::type_name::<M::Value>(),
852 )
853}
854
855fn set_type_repr<S>() -> String
856where
857 S: SetLike,
858{
859 format!("&<{}>", std::any::type_name::<S::Item>())
860}
861
862#[cfg(test)]
863mod tests {
864 use std::{
865 collections::{HashMap, HashSet},
866 fmt::Display,
867 str::FromStr,
868 };
869
870 use rstest::rstest;
871 use rust_decimal::Decimal;
872
873 use super::*;
874
875 #[rstest]
876 fn test_check_predicate_true_returns_typed_error_with_stable_display() {
877 let error = check_predicate_true(false, "the predicate was false").unwrap_err();
878
879 assert_eq!(
880 error,
881 CorrectnessError::PredicateViolation {
882 message: "the predicate was false".to_string(),
883 }
884 );
885 assert_eq!(error.to_string(), "the predicate was false");
886 }
887
888 #[rstest]
889 fn test_expect_display_returns_ok_value() {
890 let result: CorrectnessResult<i32> = Ok(42);
891 assert_eq!(result.expect_display(FAILED), 42);
892 }
893
894 #[rstest]
895 #[should_panic(expected = "Condition failed: invalid string for 'value', was empty")]
896 fn test_expect_display_panics_with_display_form_on_err() {
897 let result: CorrectnessResult<()> = Err(CorrectnessError::EmptyString {
898 param: "value".to_string(),
899 });
900 result.expect_display(FAILED);
901 }
902
903 #[rstest]
904 #[should_panic(expected = "custom prefix: the predicate was false")]
905 fn test_expect_display_uses_provided_prefix() {
906 let result: CorrectnessResult<()> = Err(CorrectnessError::PredicateViolation {
907 message: "the predicate was false".to_string(),
908 });
909 result.expect_display("custom prefix");
910 }
911
912 #[rstest]
913 #[case(false, false)]
914 #[case(true, true)]
915 fn test_check_predicate_true(#[case] predicate: bool, #[case] expected: bool) {
916 let result = check_predicate_true(predicate, "the predicate was false").is_ok();
917 assert_eq!(result, expected);
918 }
919
920 #[rstest]
921 #[case(false, true)]
922 #[case(true, false)]
923 fn test_check_predicate_false(#[case] predicate: bool, #[case] expected: bool) {
924 let result = check_predicate_false(predicate, "the predicate was true").is_ok();
925 assert_eq!(result, expected);
926 }
927
928 #[rstest]
929 #[case("a")]
930 #[case(" ")] #[case(" ")] #[case("🦀")] #[case(" a")]
934 #[case("a ")]
935 #[case("abc")]
936 fn test_check_nonempty_string_with_valid_values(#[case] s: &str) {
937 assert!(check_nonempty_string(s, "value").is_ok());
938 }
939
940 #[rstest]
941 #[case("")] fn test_check_nonempty_string_with_invalid_values(#[case] s: &str) {
943 assert!(check_nonempty_string(s, "value").is_err());
944 }
945
946 #[rstest]
947 #[case(" a")]
948 #[case("a ")]
949 #[case("a a")]
950 #[case(" a ")]
951 #[case("abc")]
952 fn test_check_valid_string_ascii_with_valid_value(#[case] s: &str) {
953 assert!(check_valid_string_ascii(s, "value").is_ok());
954 }
955
956 #[rstest]
957 #[case("")] #[case(" ")] #[case(" ")] #[case("🦀")] fn test_check_valid_string_ascii_with_invalid_values(#[case] s: &str) {
962 assert!(check_valid_string_ascii(s, "value").is_err());
963 }
964
965 #[rstest]
966 fn test_check_valid_string_ascii_returns_empty_string_error_with_stable_display() {
967 let error = check_valid_string_ascii("", "value").unwrap_err();
968
969 assert_eq!(
970 error,
971 CorrectnessError::EmptyString {
972 param: "value".to_string(),
973 }
974 );
975 assert_eq!(error.to_string(), "invalid string for 'value', was empty");
976 }
977
978 #[rstest]
979 fn test_check_valid_string_ascii_returns_non_ascii_error_with_stable_display() {
980 let error = check_valid_string_ascii("🦀", "value").unwrap_err();
981
982 assert_eq!(
983 error,
984 CorrectnessError::NonAsciiString {
985 param: "value".to_string(),
986 value: "🦀".to_string(),
987 }
988 );
989 assert_eq!(
990 error.to_string(),
991 "invalid string for 'value' contained a non-ASCII char, was '🦀'"
992 );
993 }
994
995 #[rstest]
996 fn test_check_valid_string_ascii_returns_whitespace_string_error_with_stable_display() {
997 let error = check_valid_string_ascii(" ", "value").unwrap_err();
998
999 assert_eq!(
1000 error,
1001 CorrectnessError::WhitespaceString {
1002 param: "value".to_string(),
1003 }
1004 );
1005 assert_eq!(
1006 error.to_string(),
1007 "invalid string for 'value', was all whitespace"
1008 );
1009 }
1010
1011 #[rstest]
1012 #[case(" a")]
1013 #[case("a ")]
1014 #[case("abc")]
1015 #[case("ETHUSDT")]
1016 fn test_check_valid_string_utf8_with_valid_values(#[case] s: &str) {
1017 assert!(check_valid_string_utf8(s, "value").is_ok());
1018 }
1019
1020 #[rstest]
1021 #[case("")] #[case(" ")] #[case(" ")] fn test_check_valid_string_utf8_with_invalid_values(#[case] s: &str) {
1025 assert!(check_valid_string_utf8(s, "value").is_err());
1026 }
1027
1028 #[rstest]
1029 #[case(None)]
1030 #[case(Some(" a"))]
1031 #[case(Some("a "))]
1032 #[case(Some("a a"))]
1033 #[case(Some(" a "))]
1034 #[case(Some("abc"))]
1035 fn test_check_valid_string_ascii_optional_with_valid_value(#[case] s: Option<&str>) {
1036 assert!(check_valid_string_ascii_optional(s, "value").is_ok());
1037 }
1038
1039 #[rstest]
1040 #[case("a", "a")]
1041 fn test_check_string_contains_when_does_contain(#[case] s: &str, #[case] pat: &str) {
1042 assert!(check_string_contains(s, pat, "value").is_ok());
1043 }
1044
1045 #[rstest]
1046 #[case("a", "b")]
1047 fn test_check_string_contains_when_does_not_contain(#[case] s: &str, #[case] pat: &str) {
1048 assert!(check_string_contains(s, pat, "value").is_err());
1049 }
1050
1051 #[rstest]
1052 #[case(0u8, 0u8, "left", "right", true)]
1053 #[case(1u8, 1u8, "left", "right", true)]
1054 #[case(0u8, 1u8, "left", "right", false)]
1055 #[case(1u8, 0u8, "left", "right", false)]
1056 #[case(10i32, 10i32, "left", "right", true)]
1057 #[case(10i32, 20i32, "left", "right", false)]
1058 #[case("hello", "hello", "left", "right", true)]
1059 #[case("hello", "world", "left", "right", false)]
1060 fn test_check_equal<T: PartialEq + Debug + Display>(
1061 #[case] lhs: T,
1062 #[case] rhs: T,
1063 #[case] lhs_param: &str,
1064 #[case] rhs_param: &str,
1065 #[case] expected: bool,
1066 ) {
1067 let result = check_equal(&lhs, &rhs, lhs_param, rhs_param).is_ok();
1068 assert_eq!(result, expected);
1069 }
1070
1071 #[rstest]
1072 #[case(0, 0, "left", "right", true)]
1073 #[case(1, 1, "left", "right", true)]
1074 #[case(0, 1, "left", "right", false)]
1075 #[case(1, 0, "left", "right", false)]
1076 fn test_check_equal_u8_when_equal(
1077 #[case] lhs: u8,
1078 #[case] rhs: u8,
1079 #[case] lhs_param: &str,
1080 #[case] rhs_param: &str,
1081 #[case] expected: bool,
1082 ) {
1083 let result = check_equal_u8(lhs, rhs, lhs_param, rhs_param).is_ok();
1084 assert_eq!(result, expected);
1085 }
1086
1087 #[rstest]
1088 fn test_check_equal_u8_returns_equality_mismatch_with_stable_display() {
1089 let error = check_equal_u8(1, 2, "left", "right").unwrap_err();
1090
1091 assert_eq!(
1092 error,
1093 CorrectnessError::EqualityMismatch {
1094 lhs_param: "left".to_string(),
1095 rhs_param: "right".to_string(),
1096 lhs: "1".to_string(),
1097 rhs: "2".to_string(),
1098 type_name: "u8",
1099 }
1100 );
1101 assert_eq!(
1102 error.to_string(),
1103 "'left' u8 of 1 was not equal to 'right' u8 of 2"
1104 );
1105 }
1106
1107 #[rstest]
1108 #[case(0, 0, "left", "right", true)]
1109 #[case(1, 1, "left", "right", true)]
1110 #[case(0, 1, "left", "right", false)]
1111 #[case(1, 0, "left", "right", false)]
1112 fn test_check_equal_usize_when_equal(
1113 #[case] lhs: usize,
1114 #[case] rhs: usize,
1115 #[case] lhs_param: &str,
1116 #[case] rhs_param: &str,
1117 #[case] expected: bool,
1118 ) {
1119 let result = check_equal_usize(lhs, rhs, lhs_param, rhs_param).is_ok();
1120 assert_eq!(result, expected);
1121 }
1122
1123 #[rstest]
1124 #[case(1, "value")]
1125 fn test_check_positive_u64_when_positive(#[case] value: u64, #[case] param: &str) {
1126 assert!(check_positive_u64(value, param).is_ok());
1127 }
1128
1129 #[rstest]
1130 #[case(0, "value")]
1131 fn test_check_positive_u64_when_not_positive(#[case] value: u64, #[case] param: &str) {
1132 assert!(check_positive_u64(value, param).is_err());
1133 }
1134
1135 #[rstest]
1136 #[case(1, "value")]
1137 fn test_check_positive_i64_when_positive(#[case] value: i64, #[case] param: &str) {
1138 assert!(check_positive_i64(value, param).is_ok());
1139 }
1140
1141 #[rstest]
1142 #[case(0, "value")]
1143 #[case(-1, "value")]
1144 fn test_check_positive_i64_when_not_positive(#[case] value: i64, #[case] param: &str) {
1145 assert!(check_positive_i64(value, param).is_err());
1146 }
1147
1148 #[rstest]
1149 #[case(0.0, "value")]
1150 #[case(1.0, "value")]
1151 fn test_check_non_negative_f64_when_not_negative(#[case] value: f64, #[case] param: &str) {
1152 assert!(check_non_negative_f64(value, param).is_ok());
1153 }
1154
1155 #[rstest]
1156 #[case(f64::NAN, "value")]
1157 #[case(f64::INFINITY, "value")]
1158 #[case(f64::NEG_INFINITY, "value")]
1159 #[case(-0.1, "value")]
1160 fn test_check_non_negative_f64_when_negative(#[case] value: f64, #[case] param: &str) {
1161 assert!(check_non_negative_f64(value, param).is_err());
1162 }
1163
1164 #[rstest]
1165 #[case(0, 0, 0, "value")]
1166 #[case(0, 0, 1, "value")]
1167 #[case(1, 0, 1, "value")]
1168 fn test_check_in_range_inclusive_u8_when_in_range(
1169 #[case] value: u8,
1170 #[case] l: u8,
1171 #[case] r: u8,
1172 #[case] desc: &str,
1173 ) {
1174 assert!(check_in_range_inclusive_u8(value, l, r, desc).is_ok());
1175 }
1176
1177 #[rstest]
1178 #[case(0, 1, 2, "value")]
1179 #[case(3, 1, 2, "value")]
1180 fn test_check_in_range_inclusive_u8_when_out_of_range(
1181 #[case] value: u8,
1182 #[case] l: u8,
1183 #[case] r: u8,
1184 #[case] param: &str,
1185 ) {
1186 assert!(check_in_range_inclusive_u8(value, l, r, param).is_err());
1187 }
1188
1189 #[rstest]
1190 #[case(0, 0, 0, "value")]
1191 #[case(0, 0, 1, "value")]
1192 #[case(1, 0, 1, "value")]
1193 fn test_check_in_range_inclusive_u64_when_in_range(
1194 #[case] value: u64,
1195 #[case] l: u64,
1196 #[case] r: u64,
1197 #[case] param: &str,
1198 ) {
1199 assert!(check_in_range_inclusive_u64(value, l, r, param).is_ok());
1200 }
1201
1202 #[rstest]
1203 #[case(0, 1, 2, "value")]
1204 #[case(3, 1, 2, "value")]
1205 fn test_check_in_range_inclusive_u64_when_out_of_range(
1206 #[case] value: u64,
1207 #[case] l: u64,
1208 #[case] r: u64,
1209 #[case] param: &str,
1210 ) {
1211 assert!(check_in_range_inclusive_u64(value, l, r, param).is_err());
1212 }
1213
1214 #[rstest]
1215 #[case(0, 0, 0, "value")]
1216 #[case(0, 0, 1, "value")]
1217 #[case(1, 0, 1, "value")]
1218 fn test_check_in_range_inclusive_i64_when_in_range(
1219 #[case] value: i64,
1220 #[case] l: i64,
1221 #[case] r: i64,
1222 #[case] param: &str,
1223 ) {
1224 assert!(check_in_range_inclusive_i64(value, l, r, param).is_ok());
1225 }
1226
1227 #[rstest]
1228 #[case(0.0, 0.0, 0.0, "value")]
1229 #[case(0.0, 0.0, 1.0, "value")]
1230 #[case(1.0, 0.0, 1.0, "value")]
1231 fn test_check_in_range_inclusive_f64_when_in_range(
1232 #[case] value: f64,
1233 #[case] l: f64,
1234 #[case] r: f64,
1235 #[case] param: &str,
1236 ) {
1237 assert!(check_in_range_inclusive_f64(value, l, r, param).is_ok());
1238 }
1239
1240 #[rstest]
1241 #[case(-1e16, 0.0, 0.0, "value")]
1242 #[case(1.0 + 1e16, 0.0, 1.0, "value")]
1243 fn test_check_in_range_inclusive_f64_when_out_of_range(
1244 #[case] value: f64,
1245 #[case] l: f64,
1246 #[case] r: f64,
1247 #[case] param: &str,
1248 ) {
1249 assert!(check_in_range_inclusive_f64(value, l, r, param).is_err());
1250 }
1251
1252 #[rstest]
1253 #[case(0, 1, 2, "value")]
1254 #[case(3, 1, 2, "value")]
1255 fn test_check_in_range_inclusive_i64_when_out_of_range(
1256 #[case] value: i64,
1257 #[case] l: i64,
1258 #[case] r: i64,
1259 #[case] param: &str,
1260 ) {
1261 assert!(check_in_range_inclusive_i64(value, l, r, param).is_err());
1262 }
1263
1264 #[rstest]
1265 #[case(0, 0, 0, "value")]
1266 #[case(0, 0, 1, "value")]
1267 #[case(1, 0, 1, "value")]
1268 fn test_check_in_range_inclusive_usize_when_in_range(
1269 #[case] value: usize,
1270 #[case] l: usize,
1271 #[case] r: usize,
1272 #[case] param: &str,
1273 ) {
1274 assert!(check_in_range_inclusive_usize(value, l, r, param).is_ok());
1275 }
1276
1277 #[rstest]
1278 #[case(0, 1, 2, "value")]
1279 #[case(3, 1, 2, "value")]
1280 fn test_check_in_range_inclusive_usize_when_out_of_range(
1281 #[case] value: usize,
1282 #[case] l: usize,
1283 #[case] r: usize,
1284 #[case] param: &str,
1285 ) {
1286 assert!(check_in_range_inclusive_usize(value, l, r, param).is_err());
1287 }
1288
1289 #[rstest]
1290 fn test_check_in_range_inclusive_usize_returns_out_of_range_error_with_stable_display() {
1291 let error = check_in_range_inclusive_usize(3, 1, 2, "value").unwrap_err();
1292
1293 assert_eq!(
1294 error,
1295 CorrectnessError::OutOfRange {
1296 param: "value".to_string(),
1297 min: "1".to_string(),
1298 max: "2".to_string(),
1299 value: "3".to_string(),
1300 type_name: "usize",
1301 }
1302 );
1303 assert_eq!(
1304 error.to_string(),
1305 "invalid usize for 'value' not in range [1, 2], was 3"
1306 );
1307 }
1308
1309 #[rstest]
1310 #[case(vec![], true)]
1311 #[case(vec![1_u8], false)]
1312 fn test_check_slice_empty(#[case] collection: Vec<u8>, #[case] expected: bool) {
1313 let result = check_slice_empty(collection.as_slice(), "param").is_ok();
1314 assert_eq!(result, expected);
1315 }
1316
1317 #[rstest]
1318 #[case(vec![], false)]
1319 #[case(vec![1_u8], true)]
1320 fn test_check_slice_not_empty(#[case] collection: Vec<u8>, #[case] expected: bool) {
1321 let result = check_slice_not_empty(collection.as_slice(), "param").is_ok();
1322 assert_eq!(result, expected);
1323 }
1324
1325 #[rstest]
1326 fn test_check_slice_not_empty_returns_collection_empty_error_with_stable_display() {
1327 let error = check_slice_not_empty::<u8>(&[], "param").unwrap_err();
1328
1329 assert_eq!(
1330 error,
1331 CorrectnessError::CollectionEmpty {
1332 param: "param".to_string(),
1333 collection_kind: "slice",
1334 type_repr: "&[u8]".to_string(),
1335 }
1336 );
1337 assert_eq!(error.to_string(), "the 'param' slice `&[u8]` was empty");
1338 }
1339
1340 #[rstest]
1341 #[case(HashMap::new(), true)]
1342 #[case(HashMap::from([("A".to_string(), 1_u8)]), false)]
1343 fn test_check_map_empty(#[case] map: HashMap<String, u8>, #[case] expected: bool) {
1344 let result = check_map_empty(&map, "param").is_ok();
1345 assert_eq!(result, expected);
1346 }
1347
1348 #[rstest]
1349 #[case(HashMap::new(), false)]
1350 #[case(HashMap::from([("A".to_string(), 1_u8)]), true)]
1351 fn test_check_map_not_empty(#[case] map: HashMap<String, u8>, #[case] expected: bool) {
1352 let result = check_map_not_empty(&map, "param").is_ok();
1353 assert_eq!(result, expected);
1354 }
1355
1356 #[rstest]
1357 #[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(
1361 #[case] map: &HashMap<u32, u32>,
1362 #[case] key: u32,
1363 #[case] key_name: &str,
1364 #[case] map_name: &str,
1365 #[case] expected: bool,
1366 ) {
1367 let result = check_key_not_in_map(&key, map, key_name, map_name).is_ok();
1368 assert_eq!(result, expected);
1369 }
1370
1371 #[rstest]
1372 #[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(
1376 #[case] map: &HashMap<u32, u32>,
1377 #[case] key: u32,
1378 #[case] key_name: &str,
1379 #[case] map_name: &str,
1380 #[case] expected: bool,
1381 ) {
1382 let result = check_key_in_map(&key, map, key_name, map_name).is_ok();
1383 assert_eq!(result, expected);
1384 }
1385
1386 #[rstest]
1387 fn test_check_key_in_map_returns_key_missing_error_with_stable_display() {
1388 let map = HashMap::<u32, u32>::new();
1389 let error = check_key_in_map(&5, &map, "key", "map").unwrap_err();
1390
1391 assert_eq!(
1392 error,
1393 CorrectnessError::KeyMissing {
1394 key_name: "key".to_string(),
1395 map_name: "map".to_string(),
1396 key: "5".to_string(),
1397 map_type_repr: "&<u32, u32>".to_string(),
1398 }
1399 );
1400 assert_eq!(
1401 error.to_string(),
1402 "the 'key' key 5 was not in the 'map' map `&<u32, u32>`"
1403 );
1404 }
1405
1406 #[rstest]
1407 #[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(
1411 #[case] set: &HashSet<u32>,
1412 #[case] member: u32,
1413 #[case] member_name: &str,
1414 #[case] set_name: &str,
1415 #[case] expected: bool,
1416 ) {
1417 let result = check_member_not_in_set(&member, set, member_name, set_name).is_ok();
1418 assert_eq!(result, expected);
1419 }
1420
1421 #[rstest]
1422 #[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(
1426 #[case] set: &HashSet<u32>,
1427 #[case] member: u32,
1428 #[case] member_name: &str,
1429 #[case] set_name: &str,
1430 #[case] expected: bool,
1431 ) {
1432 let result = check_member_in_set(&member, set, member_name, set_name).is_ok();
1433 assert_eq!(result, expected);
1434 }
1435
1436 #[rstest]
1437 #[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) {
1444 let value = Decimal::from_str(raw).expect("valid decimal literal");
1445 let result = super::check_positive_decimal(value, "param").is_ok();
1446 assert_eq!(result, expected);
1447 }
1448
1449 #[rstest]
1450 #[case(1, true)]
1451 #[case(u128::MAX, true)]
1452 #[case(0, false)]
1453 fn test_check_positive_u128(#[case] value: u128, #[case] expected: bool) {
1454 assert_eq!(check_positive_u128(value, "value").is_ok(), expected);
1455 }
1456
1457 #[rstest]
1458 #[case(1, true)]
1459 #[case(i128::MAX, true)]
1460 #[case(0, false)]
1461 #[case(-1, false)]
1462 #[case(i128::MIN, false)]
1463 fn test_check_positive_i128(#[case] value: i128, #[case] expected: bool) {
1464 assert_eq!(check_positive_i128(value, "value").is_ok(), expected);
1465 }
1466
1467 #[rstest]
1468 fn test_check_positive_decimal_returns_not_positive_error_with_stable_display() {
1469 let error = check_positive_decimal(Decimal::ZERO, "param").unwrap_err();
1470
1471 assert_eq!(
1472 error,
1473 CorrectnessError::NotPositive {
1474 param: "param".to_string(),
1475 value: "0".to_string(),
1476 type_name: "Decimal",
1477 }
1478 );
1479 assert_eq!(
1480 error.to_string(),
1481 "invalid Decimal for 'param' not positive, was 0"
1482 );
1483 }
1484}