1use std::borrow::Cow;
2use std::cmp::Ordering;
3use std::fmt::{Debug, Display};
4
5use crate::case_sensitivity::{
6 caseless_contains, caseless_ends_with, caseless_eq, caseless_starts_with,
7};
8
9#[cfg(feature = "secrecy")]
10use secrecy::ExposeSecret;
11
12pub trait Filterable {
48 fn get(&self, key: &str) -> FilterValue<'_>;
59}
60
61#[derive(Clone, Default)]
99pub enum FilterValue<'a> {
100 #[default]
102 Null,
103 Bool(bool),
105 Number(f64),
107 String(Cow<'a, str>),
113 Tuple(Vec<FilterValue<'a>>),
115 #[cfg(feature = "secrecy")]
118 Secret(secrecy::SecretString),
119 #[cfg(feature = "chrono")]
124 DateTime(chrono::DateTime<chrono::Utc>),
125 #[cfg(feature = "chrono")]
130 Duration(chrono::Duration),
131}
132
133impl<'a> FilterValue<'a> {
134 #[cfg(feature = "secrecy")]
160 pub fn secret(value: impl Into<String>) -> Self {
161 FilterValue::Secret(secrecy::SecretString::from(value.into()))
162 }
163
164 pub fn is_truthy(&self) -> bool {
182 match self {
183 FilterValue::Null => false,
184 FilterValue::Bool(b) => *b,
185 FilterValue::Number(n) => *n != 0.0,
186 FilterValue::String(s) => !s.is_empty(),
187 FilterValue::Tuple(v) => !v.is_empty(),
188 #[cfg(feature = "secrecy")]
189 FilterValue::Secret(s) => !s.expose_secret().is_empty(),
190 #[cfg(feature = "chrono")]
191 FilterValue::DateTime(..) => true,
192 #[cfg(feature = "chrono")]
193 FilterValue::Duration(d) => !d.is_zero(),
194 }
195 }
196
197 pub fn contains(&self, other: &FilterValue<'a>) -> bool {
222 match (self, other) {
223 (FilterValue::Tuple(a), b) => a.iter().any(|ai| ai == b),
224 (FilterValue::String(a), FilterValue::String(b)) => caseless_contains(a, b),
225 #[cfg(feature = "secrecy")]
226 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
227 caseless_contains(a.expose_secret(), b.expose_secret())
228 }
229 #[cfg(feature = "secrecy")]
230 (FilterValue::Secret(a), FilterValue::String(b)) => {
231 caseless_contains(a.expose_secret(), b)
232 }
233 #[cfg(feature = "secrecy")]
234 (FilterValue::String(a), FilterValue::Secret(b)) => {
235 caseless_contains(a, b.expose_secret())
236 }
237 _ => false,
238 }
239 }
240
241 pub fn startswith(&self, other: &FilterValue<'a>) -> bool {
259 match (self, other) {
260 (FilterValue::Tuple(a), b) => a.iter().any(|ai| ai == b),
261 (FilterValue::String(a), FilterValue::String(b)) => caseless_starts_with(a, b),
262 #[cfg(feature = "secrecy")]
263 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
264 caseless_starts_with(a.expose_secret(), b.expose_secret())
265 }
266 #[cfg(feature = "secrecy")]
267 (FilterValue::Secret(a), FilterValue::String(b)) => {
268 caseless_starts_with(a.expose_secret(), b)
269 }
270 #[cfg(feature = "secrecy")]
271 (FilterValue::String(a), FilterValue::Secret(b)) => {
272 caseless_starts_with(a, b.expose_secret())
273 }
274 _ => false,
275 }
276 }
277
278 pub fn endswith(&self, other: &FilterValue<'a>) -> bool {
296 match (self, other) {
297 (FilterValue::Tuple(a), b) => a.iter().any(|ai| ai == b),
298 (FilterValue::String(a), FilterValue::String(b)) => caseless_ends_with(a, b),
299 #[cfg(feature = "secrecy")]
300 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
301 caseless_ends_with(a.expose_secret(), b.expose_secret())
302 }
303 #[cfg(feature = "secrecy")]
304 (FilterValue::Secret(a), FilterValue::String(b)) => {
305 caseless_ends_with(a.expose_secret(), b)
306 }
307 #[cfg(feature = "secrecy")]
308 (FilterValue::String(a), FilterValue::Secret(b)) => {
309 caseless_ends_with(a, b.expose_secret())
310 }
311 _ => false,
312 }
313 }
314
315 pub fn eq_cs(&self, other: &FilterValue<'a>) -> bool {
332 match (self, other) {
333 (FilterValue::String(a), FilterValue::String(b)) => a == b,
334 #[cfg(feature = "secrecy")]
335 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
336 a.expose_secret() == b.expose_secret()
337 }
338 #[cfg(feature = "secrecy")]
339 (FilterValue::Secret(a), FilterValue::String(b)) => a.expose_secret() == b,
340 #[cfg(feature = "secrecy")]
341 (FilterValue::String(a), FilterValue::Secret(b)) => a == b.expose_secret(),
342 (FilterValue::Tuple(a), FilterValue::Tuple(b)) => {
343 a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| a.eq_cs(b))
344 }
345 _ => self == other,
346 }
347 }
348
349 pub fn contains_cs(&self, other: &FilterValue<'a>) -> bool {
366 match (self, other) {
367 (FilterValue::Tuple(a), b) => a.iter().any(|ai| ai.eq_cs(b)),
368 (FilterValue::String(a), FilterValue::String(b)) => a.contains(b.as_ref()),
369 #[cfg(feature = "secrecy")]
370 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
371 a.expose_secret().contains(b.expose_secret())
372 }
373 #[cfg(feature = "secrecy")]
374 (FilterValue::Secret(a), FilterValue::String(b)) => {
375 a.expose_secret().contains(b.as_ref())
376 }
377 #[cfg(feature = "secrecy")]
378 (FilterValue::String(a), FilterValue::Secret(b)) => a.contains(b.expose_secret()),
379 _ => false,
380 }
381 }
382
383 pub fn startswith_cs(&self, other: &FilterValue<'a>) -> bool {
397 match (self, other) {
398 (FilterValue::Tuple(a), b) => a.iter().any(|ai| ai.eq_cs(b)),
399 #[cfg(feature = "secrecy")]
400 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
401 a.expose_secret().starts_with(b.expose_secret())
402 }
403 #[cfg(feature = "secrecy")]
404 (FilterValue::Secret(a), FilterValue::String(b)) => {
405 a.expose_secret().starts_with(b.as_ref())
406 }
407 #[cfg(feature = "secrecy")]
408 (FilterValue::String(a), FilterValue::Secret(b)) => a.starts_with(b.expose_secret()),
409 (FilterValue::String(a), FilterValue::String(b)) => a.starts_with(b.as_ref()),
410 _ => false,
411 }
412 }
413
414 pub fn endswith_cs(&self, other: &FilterValue<'a>) -> bool {
428 match (self, other) {
429 (FilterValue::Tuple(a), b) => a.iter().any(|ai| ai.eq_cs(b)),
430 #[cfg(feature = "secrecy")]
431 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
432 a.expose_secret().ends_with(b.expose_secret())
433 }
434 #[cfg(feature = "secrecy")]
435 (FilterValue::Secret(a), FilterValue::String(b)) => {
436 a.expose_secret().ends_with(b.as_ref())
437 }
438 #[cfg(feature = "secrecy")]
439 (FilterValue::String(a), FilterValue::Secret(b)) => a.ends_with(b.expose_secret()),
440 (FilterValue::String(a), FilterValue::String(b)) => a.ends_with(b.as_ref()),
441 _ => false,
442 }
443 }
444}
445
446impl<'a> PartialEq for FilterValue<'a> {
447 fn eq(&self, other: &Self) -> bool {
448 match (self, other) {
449 (FilterValue::Null, FilterValue::Null) => true,
450 (FilterValue::Bool(a), FilterValue::Bool(b)) => a == b,
451 (FilterValue::Number(a), FilterValue::Number(b)) => a == b,
452 (FilterValue::String(a), FilterValue::String(b)) => caseless_eq(a, b),
453 (FilterValue::Tuple(a), FilterValue::Tuple(b)) => {
454 a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| a == b)
455 }
456 #[cfg(feature = "secrecy")]
457 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
458 caseless_eq(a.expose_secret(), b.expose_secret())
459 }
460 #[cfg(feature = "secrecy")]
461 (FilterValue::Secret(a), FilterValue::String(b)) => caseless_eq(a.expose_secret(), b),
462 #[cfg(feature = "secrecy")]
463 (FilterValue::String(a), FilterValue::Secret(b)) => caseless_eq(a, b.expose_secret()),
464 #[cfg(feature = "chrono")]
465 (FilterValue::DateTime(a), FilterValue::DateTime(b)) => a == b,
466 #[cfg(feature = "chrono")]
467 (FilterValue::Duration(a), FilterValue::Duration(b)) => a == b,
468 _ => false,
469 }
470 }
471}
472
473impl<'a> PartialOrd for FilterValue<'a> {
474 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
475 match (self, other) {
476 (FilterValue::Null, FilterValue::Null) => Some(Ordering::Equal),
477 (FilterValue::Bool(a), FilterValue::Bool(b)) => a.partial_cmp(b),
478 (FilterValue::Number(a), FilterValue::Number(b)) => a.partial_cmp(b),
479 (FilterValue::String(a), FilterValue::String(b)) => a.partial_cmp(b),
480 (FilterValue::Tuple(a), FilterValue::Tuple(b)) => {
481 if a.len() != b.len() {
482 a.len().partial_cmp(&b.len())
483 } else {
484 a.iter()
485 .zip(b.iter())
486 .map(|(x, y)| x.partial_cmp(y))
487 .find(|&cmp| cmp != Some(Ordering::Equal))
488 .unwrap_or(Some(Ordering::Equal))
489 }
490 }
491 #[cfg(feature = "secrecy")]
492 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
493 a.expose_secret().partial_cmp(b.expose_secret())
494 }
495 #[cfg(feature = "secrecy")]
496 (FilterValue::Secret(a), FilterValue::String(b)) => {
497 a.expose_secret().partial_cmp(b.as_ref())
498 }
499 #[cfg(feature = "secrecy")]
500 (FilterValue::String(a), FilterValue::Secret(b)) => {
501 a.as_ref().partial_cmp(b.expose_secret())
502 }
503 #[cfg(feature = "chrono")]
504 (FilterValue::DateTime(a), FilterValue::DateTime(b)) => a.partial_cmp(b),
505 #[cfg(feature = "chrono")]
506 (FilterValue::Duration(a), FilterValue::Duration(b)) => a.partial_cmp(b),
507 _ => None, }
509 }
510
511 fn lt(&self, other: &Self) -> bool {
512 match (self, other) {
513 (FilterValue::Null, FilterValue::Null) => true,
514 (FilterValue::Bool(a), FilterValue::Bool(b)) => a < b,
515 (FilterValue::Number(a), FilterValue::Number(b)) => a < b,
516 (FilterValue::String(a), FilterValue::String(b)) => a < b,
517 (FilterValue::Tuple(a), FilterValue::Tuple(b)) => {
518 a.len() <= b.len() && a.iter().zip(b.iter()).all(|(a, b)| a < b)
519 }
520 #[cfg(feature = "secrecy")]
521 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
522 a.expose_secret() < b.expose_secret()
523 }
524 #[cfg(feature = "secrecy")]
525 (FilterValue::Secret(a), FilterValue::String(b)) => a.expose_secret() < b.as_ref(),
526 #[cfg(feature = "secrecy")]
527 (FilterValue::String(a), FilterValue::Secret(b)) => a.as_ref() < b.expose_secret(),
528 #[cfg(feature = "chrono")]
529 (FilterValue::DateTime(a), FilterValue::DateTime(b)) => a < b,
530 #[cfg(feature = "chrono")]
531 (FilterValue::Duration(a), FilterValue::Duration(b)) => a < b,
532 _ => false,
533 }
534 }
535
536 fn le(&self, other: &Self) -> bool {
537 match (self, other) {
538 (FilterValue::Null, FilterValue::Null) => true,
539 (FilterValue::Bool(a), FilterValue::Bool(b)) => a <= b,
540 (FilterValue::Number(a), FilterValue::Number(b)) => a <= b,
541 (FilterValue::String(a), FilterValue::String(b)) => a <= b,
542 (FilterValue::Tuple(a), FilterValue::Tuple(b)) => {
543 a.len() <= b.len() && a.iter().zip(b.iter()).all(|(a, b)| a <= b)
544 }
545 #[cfg(feature = "secrecy")]
546 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
547 a.expose_secret() <= b.expose_secret()
548 }
549 #[cfg(feature = "secrecy")]
550 (FilterValue::Secret(a), FilterValue::String(b)) => a.expose_secret() <= b.as_ref(),
551 #[cfg(feature = "secrecy")]
552 (FilterValue::String(a), FilterValue::Secret(b)) => a.as_ref() <= b.expose_secret(),
553 #[cfg(feature = "chrono")]
554 (FilterValue::DateTime(a), FilterValue::DateTime(b)) => a <= b,
555 #[cfg(feature = "chrono")]
556 (FilterValue::Duration(a), FilterValue::Duration(b)) => a <= b,
557 _ => false,
558 }
559 }
560
561 fn gt(&self, other: &Self) -> bool {
562 match (self, other) {
563 (FilterValue::Null, FilterValue::Null) => true,
564 (FilterValue::Bool(a), FilterValue::Bool(b)) => a > b,
565 (FilterValue::Number(a), FilterValue::Number(b)) => a > b,
566 (FilterValue::String(a), FilterValue::String(b)) => a > b,
567 (FilterValue::Tuple(a), FilterValue::Tuple(b)) => {
568 a.len() >= b.len() && a.iter().zip(b.iter()).all(|(a, b)| a > b)
569 }
570 #[cfg(feature = "secrecy")]
571 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
572 a.expose_secret() > b.expose_secret()
573 }
574 #[cfg(feature = "secrecy")]
575 (FilterValue::Secret(a), FilterValue::String(b)) => a.expose_secret() > b.as_ref(),
576 #[cfg(feature = "secrecy")]
577 (FilterValue::String(a), FilterValue::Secret(b)) => a.as_ref() > b.expose_secret(),
578 #[cfg(feature = "chrono")]
579 (FilterValue::DateTime(a), FilterValue::DateTime(b)) => a > b,
580 #[cfg(feature = "chrono")]
581 (FilterValue::Duration(a), FilterValue::Duration(b)) => a > b,
582 _ => false,
583 }
584 }
585
586 fn ge(&self, other: &Self) -> bool {
587 match (self, other) {
588 (FilterValue::Null, FilterValue::Null) => true,
589 (FilterValue::Bool(a), FilterValue::Bool(b)) => a >= b,
590 (FilterValue::Number(a), FilterValue::Number(b)) => a >= b,
591 (FilterValue::String(a), FilterValue::String(b)) => a >= b,
592 (FilterValue::Tuple(a), FilterValue::Tuple(b)) => {
593 a.len() >= b.len() && a.iter().zip(b.iter()).all(|(a, b)| a >= b)
594 }
595 #[cfg(feature = "secrecy")]
596 (FilterValue::Secret(a), FilterValue::Secret(b)) => {
597 a.expose_secret() >= b.expose_secret()
598 }
599 #[cfg(feature = "secrecy")]
600 (FilterValue::Secret(a), FilterValue::String(b)) => a.expose_secret() >= b.as_ref(),
601 #[cfg(feature = "secrecy")]
602 (FilterValue::String(a), FilterValue::Secret(b)) => a.as_ref() >= b.expose_secret(),
603 #[cfg(feature = "chrono")]
604 (FilterValue::DateTime(a), FilterValue::DateTime(b)) => a >= b,
605 #[cfg(feature = "chrono")]
606 (FilterValue::Duration(a), FilterValue::Duration(b)) => a >= b,
607 _ => false,
608 }
609 }
610}
611
612impl<'a> Display for FilterValue<'a> {
613 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
625 match self {
626 FilterValue::Null => write!(f, "null"),
627 FilterValue::Bool(b) => write!(f, "{}", b),
628 FilterValue::Number(n) => write!(f, "{}", n),
629 FilterValue::String(s) => {
630 write!(f, "\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
631 }
632 FilterValue::Tuple(v) => {
633 write!(f, "[")?;
634 for (i, value) in v.iter().enumerate() {
635 if i > 0 {
636 write!(f, ", ")?;
637 }
638 write!(f, "{}", value)?;
639 }
640 write!(f, "]")
641 }
642 #[cfg(feature = "secrecy")]
643 FilterValue::Secret(_) => write!(f, "[REDACTED]"),
644 #[cfg(feature = "chrono")]
645 FilterValue::DateTime(dt) => {
646 write!(
647 f,
648 "{}",
649 dt.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true)
650 )
651 }
652 #[cfg(feature = "chrono")]
653 FilterValue::Duration(d) => format_duration(f, d),
654 }
655 }
656}
657
658#[cfg(feature = "chrono")]
661fn format_duration(
662 f: &mut std::fmt::Formatter<'_>,
663 duration: &chrono::Duration,
664) -> std::fmt::Result {
665 let mut remaining_ms = duration.num_milliseconds();
666 if remaining_ms == 0 {
667 return write!(f, "0s");
668 }
669
670 if remaining_ms < 0 {
671 write!(f, "-")?;
672 remaining_ms = remaining_ms.checked_neg().unwrap_or(i64::MAX);
673 }
674
675 for (unit, size_ms) in [
676 ("w", 604_800_000),
677 ("d", 86_400_000),
678 ("h", 3_600_000),
679 ("m", 60_000),
680 ("s", 1_000),
681 ("ms", 1),
682 ] {
683 let count = remaining_ms / size_ms;
684 if count > 0 {
685 write!(f, "{count}{unit}")?;
686 remaining_ms %= size_ms;
687 }
688 }
689
690 Ok(())
691}
692
693impl<'a> Debug for FilterValue<'a> {
694 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
695 write!(f, "{}", self)
696 }
697}
698
699impl<'a> From<bool> for FilterValue<'a> {
700 fn from(b: bool) -> Self {
701 FilterValue::Bool(b)
702 }
703}
704
705macro_rules! number {
706 ($t:ty) => {
707 impl<'a> From<$t> for FilterValue<'a> {
708 fn from(n: $t) -> Self {
709 FilterValue::Number(n as f64)
710 }
711 }
712 };
713}
714
715number!(i8);
716number!(u8);
717number!(i16);
718number!(u16);
719number!(f32);
720number!(i32);
721number!(u32);
722number!(f64);
723number!(i64);
724number!(u64);
725
726impl<'a> From<&'a str> for FilterValue<'a> {
727 fn from(s: &'a str) -> Self {
732 FilterValue::String(Cow::Borrowed(s))
733 }
734}
735
736impl<'a> From<String> for FilterValue<'a> {
737 fn from(s: String) -> Self {
738 FilterValue::String(Cow::Owned(s))
739 }
740}
741
742#[cfg(feature = "secrecy")]
743impl<'a> From<secrecy::SecretString> for FilterValue<'a> {
744 fn from(s: secrecy::SecretString) -> Self {
755 FilterValue::Secret(s)
756 }
757}
758
759#[cfg(feature = "chrono")]
771impl<'a> From<chrono::DateTime<chrono::Utc>> for FilterValue<'a> {
772 fn from(dt: chrono::DateTime<chrono::Utc>) -> Self {
773 FilterValue::DateTime(dt)
774 }
775}
776
777#[cfg(feature = "chrono")]
788impl<'a> From<chrono::Duration> for FilterValue<'a> {
789 fn from(d: chrono::Duration) -> Self {
790 FilterValue::Duration(d)
791 }
792}
793
794#[cfg(feature = "chrono")]
808impl<'a> From<std::time::SystemTime> for FilterValue<'a> {
809 fn from(t: std::time::SystemTime) -> Self {
810 FilterValue::DateTime(t.into())
811 }
812}
813
814impl<'a, T> From<Option<T>> for FilterValue<'a>
815where
816 T: Into<FilterValue<'a>>,
817{
818 fn from(o: Option<T>) -> Self {
819 o.map_or(FilterValue::Null, Into::into)
820 }
821}
822
823impl<'a> From<Vec<FilterValue<'a>>> for FilterValue<'a> {
824 fn from(v: Vec<FilterValue<'a>>) -> Self {
825 FilterValue::Tuple(v)
826 }
827}
828
829#[cfg(test)]
830mod tests {
831 use rstest::rstest;
832
833 use super::*;
834
835 #[rstest]
836 #[case(FilterValue::Null, false)]
837 #[case(FilterValue::Bool(false), false)]
838 #[case(FilterValue::Bool(true), true)]
839 #[case(FilterValue::Number(0.0), false)]
840 #[case(FilterValue::Number(1.0), true)]
841 #[case(FilterValue::String("".into()), false)]
842 #[case(FilterValue::String("hello".into()), true)]
843 #[case(FilterValue::Tuple(vec![]), false)]
844 #[case(FilterValue::Tuple(vec![FilterValue::Bool(true)]), true)]
845 fn test_truthy(#[case] value: FilterValue<'_>, #[case] truthy: bool) {
846 assert_eq!(value.is_truthy(), truthy);
847 }
848
849 #[test]
850 fn test_bool_comparison() {
851 assert!(FilterValue::Bool(false) < FilterValue::Bool(true));
852 assert!(FilterValue::Bool(true) > FilterValue::Bool(false));
853 assert_eq!(FilterValue::Bool(true), FilterValue::Bool(true));
854 assert_eq!(FilterValue::Bool(false), FilterValue::Bool(false));
855 }
856
857 #[test]
858 fn test_number_comparison() {
859 assert!(FilterValue::Number(1.0) < FilterValue::Number(2.0));
860 assert!(FilterValue::Number(2.0) > FilterValue::Number(1.0));
861 assert_eq!(FilterValue::Number(2.0), FilterValue::Number(2.0));
862 }
863
864 #[test]
865 fn test_string_comparison() {
866 assert!(FilterValue::String("abc".into()) < FilterValue::String("xyz".into()));
867 assert!(FilterValue::String("xyz".into()) > FilterValue::String("abc".into()));
868 assert_eq!(
869 FilterValue::String("abc".into()),
870 FilterValue::String("abc".into())
871 );
872 }
873
874 #[test]
875 fn test_string_equality_is_case_insensitive() {
876 assert_eq!(
877 FilterValue::String("Hello World".into()),
878 FilterValue::String("hello world".into())
879 );
880 assert_ne!(
881 FilterValue::String("Hello World".into()),
882 FilterValue::String("goodbye world".into())
883 );
884
885 assert_eq!(
888 FilterValue::String("JÜRGEN".into()),
889 FilterValue::String("jürgen".into())
890 );
891 assert_eq!(
892 FilterValue::String("ΛΟΓΟΣ".into()),
893 FilterValue::String("λογος".into())
894 );
895 assert_eq!(
896 FilterValue::String("straße".into()),
897 FilterValue::String("STRASSE".into())
898 );
899 }
900
901 #[test]
902 fn test_tuple_comparison() {
903 assert!(
907 FilterValue::Tuple(vec![1.into(), 2.into()])
908 < FilterValue::Tuple(vec![3.into(), 4.into()])
909 );
910 assert!(
911 FilterValue::Tuple(vec![3.into(), 4.into()])
912 > FilterValue::Tuple(vec![1.into(), 2.into()])
913 );
914
915 let short = FilterValue::Tuple(vec![1.into()]);
916 let long = FilterValue::Tuple(vec![1.into(), 2.into()]);
917 assert_eq!(short.partial_cmp(&long), Some(Ordering::Less));
918 assert_eq!(long.partial_cmp(&short), Some(Ordering::Greater));
919
920 assert_eq!(
921 FilterValue::Tuple(vec![1.into(), 2.into()]),
922 FilterValue::Tuple(vec![1.into(), 2.into()])
923 );
924 assert_ne!(
925 FilterValue::Tuple(vec![1.into(), 2.into()]),
926 FilterValue::Tuple(vec![2.into(), 1.into()])
927 );
928 }
929
930 #[rstest]
931 #[case(FilterValue::Null, FilterValue::Bool(true))]
932 #[case(FilterValue::Bool(true), FilterValue::Number(1.0))]
933 #[case(FilterValue::Number(1.0), FilterValue::String("1".into()))]
934 #[case(FilterValue::String("a".into()), FilterValue::Tuple(vec!["a".into()]))]
935 fn test_mismatched_types_are_not_equal_or_ordered(
936 #[case] left: FilterValue<'_>,
937 #[case] right: FilterValue<'_>,
938 ) {
939 assert_ne!(left, right);
940 assert_eq!(left.partial_cmp(&right), None);
941 assert!(!left.lt(&right));
942 assert!(!left.le(&right));
943 assert!(!left.gt(&right));
944 assert!(!left.ge(&right));
945 }
946
947 #[rstest]
948 #[case(true.into(), FilterValue::Bool(true))]
949 #[case(42i8.into(), FilterValue::Number(42.0))]
950 #[case(42u8.into(), FilterValue::Number(42.0))]
951 #[case(42i16.into(), FilterValue::Number(42.0))]
952 #[case(42u16.into(), FilterValue::Number(42.0))]
953 #[case(42i32.into(), FilterValue::Number(42.0))]
954 #[case(42u32.into(), FilterValue::Number(42.0))]
955 #[case(42i64.into(), FilterValue::Number(42.0))]
956 #[case(42u64.into(), FilterValue::Number(42.0))]
957 #[case(4.2f32.into(), FilterValue::Number(4.2f32 as f64))]
958 #[case(4.2f64.into(), FilterValue::Number(4.2))]
959 #[case("hello".into(), FilterValue::String("hello".into()))]
960 #[case(String::from("hello").into(), FilterValue::String("hello".into()))]
961 #[case(Some(1).into(), FilterValue::Number(1.0))]
962 #[case(None::<i32>.into(), FilterValue::Null)]
963 #[case(vec![FilterValue::Null].into(), FilterValue::Tuple(vec![FilterValue::Null]))]
964 fn test_conversions(#[case] converted: FilterValue<'_>, #[case] expected: FilterValue<'_>) {
965 assert_eq!(converted, expected);
966 }
967
968 #[rstest]
969 #[case(FilterValue::Null, "null")]
970 #[case(FilterValue::Bool(true), "true")]
971 #[case(FilterValue::Bool(false), "false")]
972 #[case(FilterValue::Number(1.5), "1.5")]
973 #[case(FilterValue::String("hello".into()), "\"hello\"")]
974 #[case(FilterValue::String("say \"hi\"".into()), "\"say \\\"hi\\\"\"")]
975 #[case(FilterValue::String("back\\slash".into()), "\"back\\\\slash\"")]
976 #[case(FilterValue::Tuple(vec![]), "[]")]
977 #[case(FilterValue::Tuple(vec![1.into(), "a".into()]), "[1, \"a\"]")]
978 fn test_display(#[case] value: FilterValue<'_>, #[case] expected: &str) {
979 assert_eq!(value.to_string(), expected);
980 assert_eq!(format!("{value:?}"), expected);
981 }
982
983 #[rstest]
984 #[case("Hello World".into(), "world".into(), true)]
985 #[case("Hello World".into(), "WORLD".into(), true)]
986 #[case("Hello World".into(), "mars".into(), false)]
987 #[case(FilterValue::Tuple(vec!["a".into(), "b".into()]), "A".into(), true)]
988 #[case(FilterValue::Tuple(vec!["a".into(), "b".into()]), "c".into(), false)]
989 #[case(FilterValue::Tuple(vec![]), FilterValue::Null, false)]
990 #[case(FilterValue::Null, FilterValue::Null, false)]
991 #[case(FilterValue::Number(12.0), FilterValue::Number(2.0), false)]
992 fn test_contains(
993 #[case] value: FilterValue<'_>,
994 #[case] other: FilterValue<'_>,
995 #[case] expected: bool,
996 ) {
997 assert_eq!(value.contains(&other), expected);
998 }
999
1000 #[rstest]
1001 #[case("Hello World".into(), "hello".into(), true)]
1002 #[case("Hello World".into(), "world".into(), false)]
1003 #[case(FilterValue::Tuple(vec!["a".into()]), "a".into(), true)]
1004 #[case(FilterValue::Null, "a".into(), false)]
1005 #[case("Hello".into(), FilterValue::Null, false)]
1006 fn test_startswith(
1007 #[case] value: FilterValue<'_>,
1008 #[case] other: FilterValue<'_>,
1009 #[case] expected: bool,
1010 ) {
1011 assert_eq!(value.startswith(&other), expected);
1012 }
1013
1014 #[rstest]
1015 #[case("Hello World".into(), "world".into(), true)]
1016 #[case("Hello World".into(), "hello".into(), false)]
1017 #[case(FilterValue::Tuple(vec!["a".into()]), "a".into(), true)]
1018 #[case(FilterValue::Null, "a".into(), false)]
1019 #[case("Hello".into(), FilterValue::Null, false)]
1020 fn test_endswith(
1021 #[case] value: FilterValue<'_>,
1022 #[case] other: FilterValue<'_>,
1023 #[case] expected: bool,
1024 ) {
1025 assert_eq!(value.endswith(&other), expected);
1026 }
1027
1028 #[test]
1029 fn test_default_is_null() {
1030 assert_eq!(FilterValue::default(), FilterValue::Null);
1031 }
1032
1033 #[rstest]
1034 #[case("Hello".into(), "Hello".into(), true)]
1035 #[case("Hello".into(), "hello".into(), false)]
1036 #[case("straße".into(), "STRASSE".into(), false)] #[case(FilterValue::Null, FilterValue::Null, true)]
1038 #[case(FilterValue::Bool(true), FilterValue::Bool(true), true)]
1039 #[case(FilterValue::Number(1.0), FilterValue::Number(1.0), true)]
1040 #[case(FilterValue::Tuple(vec!["A".into()]), FilterValue::Tuple(vec!["A".into()]), true)]
1041 #[case(FilterValue::Tuple(vec!["A".into()]), FilterValue::Tuple(vec!["a".into()]), false)]
1042 #[case("1".into(), FilterValue::Number(1.0), false)]
1043 fn test_eq_cs(
1044 #[case] left: FilterValue<'_>,
1045 #[case] right: FilterValue<'_>,
1046 #[case] expected: bool,
1047 ) {
1048 assert_eq!(left.eq_cs(&right), expected);
1049 assert_eq!(right.eq_cs(&left), expected);
1050 }
1051
1052 #[rstest]
1053 #[case("Hello World".into(), "World".into(), true)]
1054 #[case("Hello World".into(), "world".into(), false)]
1055 #[case(FilterValue::Tuple(vec!["a".into(), "B".into()]), "B".into(), true)]
1056 #[case(FilterValue::Tuple(vec!["a".into(), "B".into()]), "b".into(), false)]
1057 #[case(FilterValue::Null, FilterValue::Null, false)]
1058 #[case(FilterValue::Number(12.0), FilterValue::Number(2.0), false)]
1059 fn test_contains_cs(
1060 #[case] value: FilterValue<'_>,
1061 #[case] other: FilterValue<'_>,
1062 #[case] expected: bool,
1063 ) {
1064 assert_eq!(value.contains_cs(&other), expected);
1065 }
1066
1067 #[rstest]
1068 #[case("Hello World".into(), "Hello".into(), true)]
1069 #[case("Hello World".into(), "hello".into(), false)]
1070 #[case(FilterValue::Tuple(vec!["A".into()]), "A".into(), true)]
1071 #[case(FilterValue::Tuple(vec!["A".into()]), "a".into(), false)]
1072 #[case(FilterValue::Null, "a".into(), false)]
1073 fn test_startswith_cs(
1074 #[case] value: FilterValue<'_>,
1075 #[case] other: FilterValue<'_>,
1076 #[case] expected: bool,
1077 ) {
1078 assert_eq!(value.startswith_cs(&other), expected);
1079 }
1080
1081 #[rstest]
1082 #[case("Hello World".into(), "World".into(), true)]
1083 #[case("Hello World".into(), "WORLD".into(), false)]
1084 #[case(FilterValue::Tuple(vec!["A".into()]), "A".into(), true)]
1085 #[case(FilterValue::Tuple(vec!["A".into()]), "a".into(), false)]
1086 #[case(FilterValue::Null, "a".into(), false)]
1087 fn test_endswith_cs(
1088 #[case] value: FilterValue<'_>,
1089 #[case] other: FilterValue<'_>,
1090 #[case] expected: bool,
1091 ) {
1092 assert_eq!(value.endswith_cs(&other), expected);
1093 }
1094
1095 #[rstest]
1106 #[case("ΛΟΓΟΣ", "Σ")] #[case("ΛΟΓΟΣ", "ς")] #[case("ΛΟΓΟΣ", "σ")] #[case("λογος", "Σ")] fn test_greek_sigma_forms_are_equivalent(#[case] haystack: &str, #[case] needle: &str) {
1111 let haystack: FilterValue<'_> = haystack.into();
1112 let needle: FilterValue<'_> = needle.into();
1113
1114 assert!(haystack.endswith(&needle));
1115 assert!(haystack.contains(&needle));
1116 }
1117
1118 #[rstest]
1122 #[case("İstanbul", "i\u{307}stanbul", true)] #[case("İstanbul", "\u{307}stanbul", true)] #[case("İstanbul", "istanbul", false)] #[case("straße", "STRASSE", true)] #[case("groß", "ss", true)]
1127 #[case("gross", "ß", true)] fn test_multi_char_lowercase_expansions(
1129 #[case] haystack: &str,
1130 #[case] needle: &str,
1131 #[case] expected: bool,
1132 ) {
1133 let haystack: FilterValue<'_> = haystack.into();
1134 let needle: FilterValue<'_> = needle.into();
1135
1136 assert_eq!(haystack.contains(&needle), expected);
1137 }
1138
1139 #[cfg(feature = "secrecy")]
1140 mod secrecy_tests {
1141 use super::*;
1142
1143 #[rstest]
1144 #[case(FilterValue::secret(""), false)]
1145 #[case(FilterValue::secret("hunter2"), true)]
1146 fn test_secret_truthy(#[case] value: FilterValue<'_>, #[case] truthy: bool) {
1147 assert_eq!(value.is_truthy(), truthy);
1148 }
1149
1150 #[rstest]
1151 #[case(FilterValue::secret("hunter2"), FilterValue::secret("hunter2"), true)]
1152 #[case(FilterValue::secret("hunter2"), FilterValue::secret("HUNTER2"), true)]
1153 #[case(
1154 FilterValue::secret("hunter2"),
1155 FilterValue::secret("swordfish"),
1156 false
1157 )]
1158 #[case(FilterValue::secret("hunter2"), "hunter2".into(), true)]
1159 #[case(FilterValue::secret("hunter2"), "HUNTER2".into(), true)]
1160 #[case("HUNTER2".into(), FilterValue::secret("hunter2"), true)]
1161 #[case("swordfish".into(), FilterValue::secret("hunter2"), false)]
1162 fn test_secret_equality(
1163 #[case] left: FilterValue<'_>,
1164 #[case] right: FilterValue<'_>,
1165 #[case] equal: bool,
1166 ) {
1167 assert_eq!(left == right, equal);
1168 assert_eq!(left != right, !equal);
1169 }
1170
1171 #[rstest]
1172 #[case(FilterValue::secret("abc"), FilterValue::secret("xyz"))]
1173 #[case(FilterValue::secret("abc"), "xyz".into())]
1174 #[case("abc".into(), FilterValue::secret("xyz"))]
1175 fn test_secret_ordering(#[case] smaller: FilterValue<'_>, #[case] larger: FilterValue<'_>) {
1176 assert_eq!(smaller.partial_cmp(&larger), Some(Ordering::Less));
1177 assert_eq!(larger.partial_cmp(&smaller), Some(Ordering::Greater));
1178 assert!(smaller < larger);
1179 assert!(smaller <= larger);
1180 assert!(larger > smaller);
1181 assert!(larger >= smaller);
1182 assert!(!smaller.gt(&larger));
1183 assert!(!smaller.ge(&larger));
1184 assert!(!larger.lt(&smaller));
1185 assert!(!larger.le(&smaller));
1186 }
1187
1188 #[rstest]
1189 #[case(FilterValue::secret("Hello World"), "world".into(), true)]
1190 #[case(FilterValue::secret("Hello World"), "mars".into(), false)]
1191 #[case("Hello World".into(), FilterValue::secret("WORLD"), true)]
1192 #[case("Hello World".into(), FilterValue::secret("mars"), false)]
1193 #[case(FilterValue::secret("Hello World"), FilterValue::secret("WORLD"), true)]
1194 #[case(FilterValue::Tuple(vec![FilterValue::secret("a"), "b".into()]), "A".into(), true)]
1195 #[case(FilterValue::Tuple(vec!["a".into(), "b".into()]), FilterValue::secret("B"), true)]
1196 #[case(FilterValue::Tuple(vec!["a".into(), "b".into()]), FilterValue::secret("c"), false)]
1197 fn test_secret_contains(
1198 #[case] value: FilterValue<'_>,
1199 #[case] other: FilterValue<'_>,
1200 #[case] expected: bool,
1201 ) {
1202 assert_eq!(value.contains(&other), expected);
1203 }
1204
1205 #[rstest]
1206 #[case(FilterValue::secret("Hello World"), "hello".into(), true)]
1207 #[case(FilterValue::secret("Hello World"), "world".into(), false)]
1208 #[case("Hello World".into(), FilterValue::secret("HELLO"), true)]
1209 #[case("Hello World".into(), FilterValue::secret("world"), false)]
1210 #[case(FilterValue::secret("Hello World"), FilterValue::secret("HELLO"), true)]
1211 fn test_secret_startswith(
1212 #[case] value: FilterValue<'_>,
1213 #[case] other: FilterValue<'_>,
1214 #[case] expected: bool,
1215 ) {
1216 assert_eq!(value.startswith(&other), expected);
1217 }
1218
1219 #[rstest]
1220 #[case(FilterValue::secret("Hello World"), "WORLD".into(), true)]
1221 #[case(FilterValue::secret("Hello World"), "hello".into(), false)]
1222 #[case("Hello World".into(), FilterValue::secret("world"), true)]
1223 #[case("Hello World".into(), FilterValue::secret("hello"), false)]
1224 #[case(FilterValue::secret("Hello World"), FilterValue::secret("world"), true)]
1225 fn test_secret_endswith(
1226 #[case] value: FilterValue<'_>,
1227 #[case] other: FilterValue<'_>,
1228 #[case] expected: bool,
1229 ) {
1230 assert_eq!(value.endswith(&other), expected);
1231 }
1232
1233 #[rstest]
1234 #[case(FilterValue::Null)]
1235 #[case(FilterValue::Bool(true))]
1236 #[case(FilterValue::Number(1.0))]
1237 #[case(FilterValue::Tuple(vec!["hunter2".into()]))]
1238 fn test_secrets_are_not_equal_or_ordered_against_other_types(
1239 #[case] other: FilterValue<'_>,
1240 ) {
1241 let secret = FilterValue::secret("hunter2");
1242 assert_ne!(secret, other);
1243 assert_ne!(other, secret);
1244 assert_eq!(secret.partial_cmp(&other), None);
1245 assert_eq!(other.partial_cmp(&secret), None);
1246 assert!(!secret.lt(&other));
1247 assert!(!secret.le(&other));
1248 assert!(!secret.gt(&other));
1249 assert!(!secret.ge(&other));
1250 }
1251
1252 #[rstest]
1253 #[case(FilterValue::secret("hunter2"), "[REDACTED]")]
1254 #[case(FilterValue::secret(""), "[REDACTED]")]
1255 #[case(
1256 FilterValue::Tuple(vec!["a".into(), FilterValue::secret("hunter2"), 1.into()]),
1257 "[\"a\", [REDACTED], 1]"
1258 )]
1259 fn test_secret_display_is_redacted(#[case] value: FilterValue<'_>, #[case] expected: &str) {
1260 assert_eq!(value.to_string(), expected);
1261 assert_eq!(format!("{value:?}"), expected);
1262 assert!(!value.to_string().contains("hunter2"));
1263 assert!(!format!("{value:?}").contains("hunter2"));
1264 }
1265
1266 #[test]
1267 fn test_secret_conversions() {
1268 let secret: FilterValue<'_> = secrecy::SecretString::from("hunter2").into();
1269 assert_eq!(secret, FilterValue::secret("hunter2"));
1270 assert!(matches!(secret, FilterValue::Secret(_)));
1271 assert!(matches!(
1272 FilterValue::secret(String::from("hunter2")),
1273 FilterValue::Secret(_)
1274 ));
1275 }
1276
1277 #[rstest]
1280 #[case("hunter2", "hunter2")]
1281 #[case("hunter2", "HUNTER2")]
1282 #[case("hunter2", "swordfish")]
1283 #[case("abc", "abd")]
1284 #[case("abd", "abc")]
1285 #[case("Hello World", "WORLD")]
1286 #[case("Hello World", "hello")]
1287 #[case("", "")]
1288 #[case("", "a")]
1289 #[case("ÜBER", "über")]
1290 fn test_secrets_behave_exactly_like_strings(
1291 #[case] secret: &'static str,
1292 #[case] other: &'static str,
1293 ) {
1294 let as_secret = FilterValue::secret(secret);
1295 let as_string = FilterValue::String(secret.into());
1296 let other = FilterValue::String(other.into());
1297
1298 assert_eq!(
1299 as_secret == other,
1300 as_string == other,
1301 "{secret} == {other}"
1302 );
1303 assert_eq!(
1304 other == as_secret,
1305 other == as_string,
1306 "{other} == {secret}"
1307 );
1308 assert_eq!(
1309 as_secret.partial_cmp(&other),
1310 as_string.partial_cmp(&other),
1311 "{secret} cmp {other}"
1312 );
1313 assert_eq!(
1314 other.partial_cmp(&as_secret),
1315 other.partial_cmp(&as_string),
1316 "{other} cmp {secret}"
1317 );
1318 assert_eq!(as_secret < other, as_string < other, "{secret} < {other}");
1319 assert_eq!(other < as_secret, other < as_string, "{other} < {secret}");
1320 assert_eq!(
1321 as_secret <= other,
1322 as_string <= other,
1323 "{secret} <= {other}"
1324 );
1325 assert_eq!(
1326 other <= as_secret,
1327 other <= as_string,
1328 "{other} <= {secret}"
1329 );
1330 assert_eq!(as_secret > other, as_string > other, "{secret} > {other}");
1331 assert_eq!(other > as_secret, other > as_string, "{other} > {secret}");
1332 assert_eq!(
1333 as_secret >= other,
1334 as_string >= other,
1335 "{secret} >= {other}"
1336 );
1337 assert_eq!(
1338 other >= as_secret,
1339 other >= as_string,
1340 "{other} >= {secret}"
1341 );
1342 assert_eq!(
1343 as_secret.contains(&other),
1344 as_string.contains(&other),
1345 "{secret} contains {other}"
1346 );
1347 assert_eq!(
1348 other.contains(&as_secret),
1349 other.contains(&as_string),
1350 "{other} contains {secret}"
1351 );
1352 assert_eq!(
1353 as_secret.startswith(&other),
1354 as_string.startswith(&other),
1355 "{secret} starts with {other}"
1356 );
1357 assert_eq!(
1358 other.startswith(&as_secret),
1359 other.startswith(&as_string),
1360 "{other} starts with {secret}"
1361 );
1362 assert_eq!(
1363 as_secret.endswith(&other),
1364 as_string.endswith(&other),
1365 "{secret} ends with {other}"
1366 );
1367 assert_eq!(
1368 other.endswith(&as_secret),
1369 other.endswith(&as_string),
1370 "{other} ends with {secret}"
1371 );
1372 assert_eq!(
1373 as_secret.is_truthy(),
1374 as_string.is_truthy(),
1375 "{secret} is_truthy"
1376 );
1377 }
1378 }
1379
1380 #[cfg(feature = "chrono")]
1381 mod chrono_tests {
1382 use super::*;
1383 use chrono::{Duration, TimeZone, Utc};
1384
1385 fn datetime() -> chrono::DateTime<Utc> {
1386 Utc.with_ymd_and_hms(2026, 6, 12, 13, 30, 45).unwrap()
1387 }
1388
1389 #[test]
1390 fn test_truthiness() {
1391 assert!(FilterValue::DateTime(datetime()).is_truthy());
1392 assert!(FilterValue::Duration(Duration::seconds(1)).is_truthy());
1393 assert!(FilterValue::Duration(Duration::seconds(-1)).is_truthy());
1394 assert!(!FilterValue::Duration(Duration::zero()).is_truthy());
1395 }
1396
1397 #[test]
1398 fn test_datetime_comparison() {
1399 let earlier = FilterValue::DateTime(datetime());
1400 let later = FilterValue::DateTime(datetime() + Duration::minutes(5));
1401
1402 assert!(earlier < later);
1403 assert!(later > earlier);
1404 assert!(earlier <= later);
1405 assert!(later >= earlier);
1406 assert_eq!(earlier, FilterValue::DateTime(datetime()));
1407 assert_ne!(earlier, later);
1408 assert_eq!(earlier.partial_cmp(&later), Some(Ordering::Less));
1409 }
1410
1411 #[test]
1412 fn test_duration_comparison() {
1413 let shorter = FilterValue::Duration(Duration::minutes(5));
1414 let longer = FilterValue::Duration(Duration::hours(1));
1415
1416 assert!(shorter < longer);
1417 assert!(longer > shorter);
1418 assert!(shorter <= longer);
1419 assert!(longer >= shorter);
1420 assert_eq!(shorter, FilterValue::Duration(Duration::seconds(300)));
1421 assert_ne!(shorter, longer);
1422 assert_eq!(shorter.partial_cmp(&longer), Some(Ordering::Less));
1423 }
1424
1425 #[rstest]
1426 #[case(
1427 FilterValue::DateTime(datetime()),
1428 FilterValue::Duration(Duration::minutes(5))
1429 )]
1430 #[case(FilterValue::DateTime(datetime()), FilterValue::Number(1.0))]
1431 #[case(
1432 FilterValue::Duration(Duration::minutes(5)),
1433 FilterValue::Number(300.0)
1434 )]
1435 #[case(FilterValue::Duration(Duration::minutes(5)), FilterValue::String("5m".into()))]
1436 #[case(FilterValue::DateTime(datetime()), FilterValue::Null)]
1437 fn test_mismatched_types_are_not_equal_or_ordered(
1438 #[case] left: FilterValue<'_>,
1439 #[case] right: FilterValue<'_>,
1440 ) {
1441 assert_ne!(left, right);
1442 assert_eq!(left.partial_cmp(&right), None);
1443 assert!(!left.lt(&right));
1444 assert!(!left.le(&right));
1445 assert!(!left.gt(&right));
1446 assert!(!left.ge(&right));
1447 }
1448
1449 #[rstest]
1450 #[case(FilterValue::Duration(Duration::zero()), "0s")]
1451 #[case(FilterValue::Duration(Duration::milliseconds(500)), "500ms")]
1452 #[case(FilterValue::Duration(Duration::seconds(90)), "1m30s")]
1453 #[case(FilterValue::Duration(Duration::minutes(90)), "1h30m")]
1454 #[case(FilterValue::Duration(Duration::hours(26)), "1d2h")]
1455 #[case(FilterValue::Duration(Duration::days(15)), "2w1d")]
1456 #[case(
1457 FilterValue::Duration(Duration::milliseconds(90_061_001)),
1458 "1d1h1m1s1ms"
1459 )]
1460 #[case(FilterValue::Duration(Duration::minutes(-90)), "-1h30m")]
1461 fn test_duration_display(#[case] value: FilterValue<'_>, #[case] expected: &str) {
1462 assert_eq!(value.to_string(), expected);
1463 }
1464
1465 #[test]
1466 fn test_datetime_display_is_rfc3339() {
1467 assert_eq!(
1468 FilterValue::DateTime(datetime()).to_string(),
1469 "2026-06-12T13:30:45Z"
1470 );
1471 }
1472
1473 #[test]
1474 fn test_conversions() {
1475 assert_eq!(
1476 FilterValue::from(datetime()),
1477 FilterValue::DateTime(datetime())
1478 );
1479 assert_eq!(
1480 FilterValue::from(Duration::minutes(5)),
1481 FilterValue::Duration(Duration::minutes(5))
1482 );
1483 assert_eq!(
1484 FilterValue::from(std::time::SystemTime::UNIX_EPOCH),
1485 FilterValue::DateTime(Utc.timestamp_opt(0, 0).unwrap())
1486 );
1487 }
1488
1489 #[test]
1490 fn test_contains_and_friends_are_false_for_temporal_values() {
1491 let dt = FilterValue::DateTime(datetime());
1492 let d = FilterValue::Duration(Duration::minutes(5));
1493
1494 assert!(!dt.contains(&d));
1495 assert!(!dt.startswith(&d));
1496 assert!(!dt.endswith(&d));
1497 assert!(!d.contains(&dt));
1498 assert!(!d.startswith(&dt));
1499 assert!(!d.endswith(&dt));
1500
1501 let tuple = FilterValue::Tuple(vec![FilterValue::Duration(Duration::minutes(5))]);
1503 assert!(tuple.contains(&d));
1504 }
1505 }
1506}