1use relay_pattern::{CaseInsensitive, TypedPatterns};
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use crate::{Getter, Val};
10
11#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
13#[serde(rename_all = "camelCase")]
14pub struct EqCondOptions {
15 #[serde(default)]
19 pub ignore_case: bool,
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct EqCondition {
31 pub name: String,
33
34 pub value: Value,
39
40 #[serde(default, skip_serializing_if = "is_default")]
42 pub options: EqCondOptions,
43}
44
45impl EqCondition {
46 pub fn new(field: impl Into<String>, value: impl Into<Value>) -> Self {
53 Self {
54 name: field.into(),
55 value: value.into(),
56 options: EqCondOptions { ignore_case: false },
57 }
58 }
59
60 pub fn ignore_case(mut self) -> Self {
64 self.options.ignore_case = true;
65 self
66 }
67
68 fn cmp(&self, left: &str, right: &str) -> bool {
69 if self.options.ignore_case {
70 unicase::eq(left, right)
71 } else {
72 left == right
73 }
74 }
75
76 fn matches<T>(&self, instance: &T) -> bool
77 where
78 T: Getter + ?Sized,
79 {
80 match (instance.get_value(self.name.as_str()), &self.value) {
81 (None, Value::Null) => true,
82 (Some(Val::String(f)), Value::String(ref val)) => self.cmp(f, val),
83 (Some(Val::String(f)), Value::Array(ref arr)) => arr
84 .iter()
85 .filter_map(|v| v.as_str())
86 .any(|v| self.cmp(v, f)),
87 (Some(Val::Uuid(f)), Value::String(ref val)) => Some(f) == val.parse().ok(),
88 (Some(Val::Bool(f)), Value::Bool(v)) => f == *v,
89 _ => false,
90 }
91 }
92}
93
94fn is_default<T: Default + PartialEq>(t: &T) -> bool {
96 *t == T::default()
97}
98
99macro_rules! impl_cmp_condition {
100 ($struct_name:ident, $operator:tt, $doc:literal) => {
101 #[doc = $doc]
102 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105 pub struct $struct_name {
106 pub name: String,
108 pub value: Value,
110 }
111
112 impl $struct_name {
113 pub fn new(field: impl Into<String>, value: impl Into<Value>) -> Self {
115 Self {
116 name: field.into(),
117 value: value.into(),
118 }
119 }
120
121 fn matches<T>(&self, instance: &T) -> bool
122 where
123 T: Getter + ?Sized,
124 {
125 let Some(value) = instance.get_value(self.name.as_str()) else {
126 return false;
127 };
128
129 if let (Some(a), Some(b)) = (value.as_i64(), self.value.as_i64()) {
134 a $operator b
135 } else if let (Some(a), Some(b)) = (value.as_u64(), self.value.as_u64()) {
136 a $operator b
137 } else if let (Some(a), Some(b)) = (value.as_f64(), self.value.as_f64()) {
138 a $operator b
139 } else if let (Some(a), Some(b)) = (value.as_str(), self.value.as_str()) {
140 a $operator b
141 } else {
142 false
143 }
144 }
145 }
146 }
147}
148
149impl_cmp_condition!(GteCondition, >=, "A condition that applies `>=`.");
150impl_cmp_condition!(LteCondition, <=, "A condition that applies `<=`.");
151impl_cmp_condition!(GtCondition, >, "A condition that applies `>`.");
152impl_cmp_condition!(LtCondition, <, "A condition that applies `<`.");
153
154#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160pub struct GlobCondition {
161 pub name: String,
163 pub value: TypedPatterns<CaseInsensitive>,
167}
168
169impl GlobCondition {
170 pub fn new(field: impl Into<String>, value: impl IntoStrings) -> Self {
172 Self {
173 name: field.into(),
174 value: TypedPatterns::from(value.into_strings()),
175 }
176 }
177
178 fn matches<T>(&self, instance: &T) -> bool
179 where
180 T: Getter + ?Sized,
181 {
182 match instance.get_value(self.name.as_str()) {
183 Some(Val::String(s)) => self.value.is_match(s),
184 _ => false,
185 }
186 }
187}
188
189pub trait IntoStrings {
191 fn into_strings(self) -> Vec<String>;
193}
194
195impl IntoStrings for &'_ str {
196 fn into_strings(self) -> Vec<String> {
197 vec![self.to_owned()]
198 }
199}
200
201impl IntoStrings for String {
202 fn into_strings(self) -> Vec<String> {
203 vec![self]
204 }
205}
206
207impl IntoStrings for std::borrow::Cow<'_, str> {
208 fn into_strings(self) -> Vec<String> {
209 vec![self.into_owned()]
210 }
211}
212
213impl IntoStrings for &'_ [&'_ str] {
214 fn into_strings(self) -> Vec<String> {
215 self.iter().copied().map(str::to_owned).collect()
216 }
217}
218
219impl IntoStrings for &'_ [String] {
220 fn into_strings(self) -> Vec<String> {
221 self.to_vec()
222 }
223}
224
225impl IntoStrings for Vec<&'_ str> {
226 fn into_strings(self) -> Vec<String> {
227 self.into_iter().map(str::to_owned).collect()
228 }
229}
230
231impl IntoStrings for Vec<String> {
232 fn into_strings(self) -> Vec<String> {
233 self
234 }
235}
236
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
244pub struct OrCondition {
245 pub inner: Vec<RuleCondition>,
247}
248
249impl OrCondition {
250 fn supported(&self) -> bool {
251 self.inner.iter().all(RuleCondition::supported)
252 }
253
254 fn matches<T>(&self, value: &T) -> bool
255 where
256 T: Getter + ?Sized,
257 {
258 self.inner.iter().any(|cond| cond.matches(value))
259 }
260}
261
262#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
269pub struct AndCondition {
270 pub inner: Vec<RuleCondition>,
272}
273
274impl AndCondition {
275 fn supported(&self) -> bool {
276 self.inner.iter().all(RuleCondition::supported)
277 }
278 fn matches<T>(&self, value: &T) -> bool
279 where
280 T: Getter + ?Sized,
281 {
282 self.inner.iter().all(|cond| cond.matches(value))
283 }
284}
285
286#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
292pub struct NotCondition {
293 pub inner: Box<RuleCondition>,
295}
296
297impl NotCondition {
298 fn supported(&self) -> bool {
299 self.inner.supported()
300 }
301
302 fn matches<T>(&self, value: &T) -> bool
303 where
304 T: Getter + ?Sized,
305 {
306 !self.inner.matches(value)
307 }
308}
309
310#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
315pub struct AnyCondition {
316 pub name: String,
318 pub inner: Box<RuleCondition>,
320}
321
322impl AnyCondition {
323 pub fn new(field: impl Into<String>, inner: RuleCondition) -> Self {
325 Self {
326 name: field.into(),
327 inner: Box::new(inner),
328 }
329 }
330
331 fn supported(&self) -> bool {
332 self.inner.supported()
333 }
334 fn matches<T>(&self, instance: &T) -> bool
335 where
336 T: Getter + ?Sized,
337 {
338 let Some(mut getter_iter) = instance.get_iter(self.name.as_str()) else {
339 return false;
340 };
341
342 getter_iter.any(|g| self.inner.matches(g))
343 }
344}
345
346#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
350pub struct AllCondition {
351 pub name: String,
353 pub inner: Box<RuleCondition>,
355}
356
357impl AllCondition {
358 pub fn new(field: impl Into<String>, inner: RuleCondition) -> Self {
361 Self {
362 name: field.into(),
363 inner: Box::new(inner),
364 }
365 }
366
367 fn supported(&self) -> bool {
368 self.inner.supported()
369 }
370 fn matches<T>(&self, instance: &T) -> bool
371 where
372 T: Getter + ?Sized,
373 {
374 let Some(mut getter_iter) = instance.get_iter(self.name.as_str()) else {
375 return false;
376 };
377
378 getter_iter.all(|g| self.inner.matches(g))
379 }
380}
381
382#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
412#[serde(rename_all = "camelCase", tag = "op")]
413pub enum RuleCondition {
414 Eq(EqCondition),
429
430 Gte(GteCondition),
440
441 Lte(LteCondition),
451
452 Gt(GtCondition),
462
463 Lt(LtCondition),
473
474 Glob(GlobCondition),
484
485 Or(OrCondition),
496
497 And(AndCondition),
508
509 Not(NotCondition),
519
520 Any(AnyCondition),
533
534 All(AllCondition),
546
547 #[serde(other)]
549 Unsupported,
550}
551
552impl RuleCondition {
553 pub fn all() -> Self {
555 Self::And(AndCondition { inner: Vec::new() })
556 }
557
558 pub fn never() -> Self {
560 Self::Or(OrCondition { inner: Vec::new() })
561 }
562
563 pub fn eq(field: impl Into<String>, value: impl Into<Value>) -> Self {
585 Self::Eq(EqCondition::new(field, value))
586 }
587
588 pub fn eq_ignore_case(field: impl Into<String>, value: impl Into<Value>) -> Self {
602 Self::Eq(EqCondition::new(field, value).ignore_case())
603 }
604
605 pub fn glob(field: impl Into<String>, value: impl IntoStrings) -> Self {
619 Self::Glob(GlobCondition::new(field, value))
620 }
621
622 pub fn gt(field: impl Into<String>, value: impl Into<Value>) -> Self {
632 Self::Gt(GtCondition::new(field, value))
633 }
634
635 pub fn gte(field: impl Into<String>, value: impl Into<Value>) -> Self {
645 Self::Gte(GteCondition::new(field, value))
646 }
647
648 pub fn lt(field: impl Into<String>, value: impl Into<Value>) -> Self {
658 Self::Lt(LtCondition::new(field, value))
659 }
660
661 pub fn lte(field: impl Into<String>, value: impl Into<Value>) -> Self {
671 Self::Lte(LteCondition::new(field, value))
672 }
673
674 pub fn and(mut self, other: RuleCondition) -> Self {
687 if let Self::And(ref mut condition) = self {
688 condition.inner.push(other);
689 self
690 } else {
691 Self::And(AndCondition {
692 inner: vec![self, other],
693 })
694 }
695 }
696
697 pub fn or(mut self, other: RuleCondition) -> Self {
710 if let Self::Or(ref mut condition) = self {
711 condition.inner.push(other);
712 self
713 } else {
714 Self::Or(OrCondition {
715 inner: vec![self, other],
716 })
717 }
718 }
719
720 pub fn negate(self) -> Self {
732 match self {
733 Self::Not(condition) => *condition.inner,
734 other => Self::Not(NotCondition {
735 inner: Box::new(other),
736 }),
737 }
738 }
739
740 pub fn for_any(field: impl Into<String>, inner: RuleCondition) -> Self {
752 Self::Any(AnyCondition::new(field, inner))
753 }
754
755 pub fn for_all(field: impl Into<String>, inner: RuleCondition) -> Self {
767 Self::All(AllCondition::new(field, inner))
768 }
769
770 pub fn supported(&self) -> bool {
774 match self {
775 RuleCondition::Unsupported => false,
776 RuleCondition::Gte(_)
778 | RuleCondition::Lte(_)
779 | RuleCondition::Gt(_)
780 | RuleCondition::Lt(_)
781 | RuleCondition::Eq(_)
782 | RuleCondition::Glob(_) => true,
783 RuleCondition::And(rules) => rules.supported(),
785 RuleCondition::Or(rules) => rules.supported(),
786 RuleCondition::Not(rule) => rule.supported(),
787 RuleCondition::Any(rule) => rule.supported(),
788 RuleCondition::All(rule) => rule.supported(),
789 }
790 }
791
792 pub fn matches<T>(&self, value: &T) -> bool
794 where
795 T: Getter + ?Sized,
796 {
797 match self {
798 RuleCondition::Eq(condition) => condition.matches(value),
799 RuleCondition::Lte(condition) => condition.matches(value),
800 RuleCondition::Gte(condition) => condition.matches(value),
801 RuleCondition::Gt(condition) => condition.matches(value),
802 RuleCondition::Lt(condition) => condition.matches(value),
803 RuleCondition::Glob(condition) => condition.matches(value),
804 RuleCondition::And(conditions) => conditions.matches(value),
805 RuleCondition::Or(conditions) => conditions.matches(value),
806 RuleCondition::Not(condition) => condition.matches(value),
807 RuleCondition::Any(condition) => condition.matches(value),
808 RuleCondition::All(condition) => condition.matches(value),
809 RuleCondition::Unsupported => false,
810 }
811 }
812}
813
814impl std::ops::BitAnd for RuleCondition {
815 type Output = Self;
816
817 fn bitand(self, rhs: Self) -> Self::Output {
818 self.and(rhs)
819 }
820}
821
822impl std::ops::BitOr for RuleCondition {
823 type Output = Self;
824
825 fn bitor(self, rhs: Self) -> Self::Output {
826 self.or(rhs)
827 }
828}
829
830impl std::ops::Not for RuleCondition {
831 type Output = Self;
832
833 fn not(self) -> Self::Output {
834 self.negate()
835 }
836}
837
838#[cfg(test)]
839mod tests {
840 use super::*;
841 use crate::GetterIter;
842
843 #[derive(Debug)]
844 struct Exception {
845 name: String,
846 }
847
848 impl Getter for Exception {
849 fn get_value(&self, path: &str) -> Option<Val<'_>> {
850 Some(match path {
851 "name" => self.name.as_str().into(),
852 _ => return None,
853 })
854 }
855 }
856
857 struct Trace {
858 transaction: String,
859 release: String,
860 environment: String,
861 user_segment: String,
862 exceptions: Vec<Exception>,
863 }
864
865 impl Getter for Trace {
866 fn get_value(&self, path: &str) -> Option<Val<'_>> {
867 Some(match path.strip_prefix("trace.")? {
868 "transaction" => self.transaction.as_str().into(),
869 "release" => self.release.as_str().into(),
870 "environment" => self.environment.as_str().into(),
871 "user.segment" => self.user_segment.as_str().into(),
872 _ => {
873 return None;
874 }
875 })
876 }
877
878 fn get_iter(&self, path: &str) -> Option<GetterIter<'_>> {
879 Some(match path.strip_prefix("trace.")? {
880 "exceptions" => GetterIter::new(self.exceptions.iter()),
881 _ => return None,
882 })
883 }
884 }
885
886 fn mock_trace() -> Trace {
887 Trace {
888 transaction: "transaction1".to_string(),
889 release: "1.1.1".to_string(),
890 environment: "debug".to_string(),
891 user_segment: "vip".to_string(),
892 exceptions: vec![
893 Exception {
894 name: "NullPointerException".to_string(),
895 },
896 Exception {
897 name: "NullUser".to_string(),
898 },
899 ],
900 }
901 }
902
903 #[test]
904 fn deserialize() {
905 let serialized_rules = r#"[
906 {
907 "op":"eq",
908 "name": "field_1",
909 "value": ["UPPER","lower"],
910 "options":{
911 "ignoreCase": true
912 }
913 },
914 {
915 "op":"eq",
916 "name": "field_2",
917 "value": ["UPPER","lower"]
918 },
919 {
920 "op":"glob",
921 "name": "field_3",
922 "value": ["1.2.*","2.*"]
923 },
924 {
925 "op":"not",
926 "inner": {
927 "op":"glob",
928 "name": "field_4",
929 "value": ["1.*"]
930 }
931 },
932 {
933 "op":"and",
934 "inner": [{
935 "op":"glob",
936 "name": "field_5",
937 "value": ["2.*"]
938 }]
939 },
940 {
941 "op":"or",
942 "inner": [{
943 "op":"glob",
944 "name": "field_6",
945 "value": ["3.*"]
946 }]
947 },
948 {
949 "op": "any",
950 "name": "obj.exceptions",
951 "inner": {
952 "op": "glob",
953 "name": "value",
954 "value": ["*Exception"]
955 }
956 },
957 {
958 "op": "all",
959 "name": "obj.exceptions",
960 "inner": {
961 "op": "glob",
962 "name": "value",
963 "value": ["*Exception"]
964 }
965 }
966 ]"#;
967
968 let rules: Result<Vec<RuleCondition>, _> = serde_json::from_str(serialized_rules);
969 assert!(rules.is_ok());
970 let rules = rules.unwrap();
971 insta::assert_ron_snapshot!(rules, @r###"
972 [
973 EqCondition(
974 op: "eq",
975 name: "field_1",
976 value: [
977 "UPPER",
978 "lower",
979 ],
980 options: EqCondOptions(
981 ignoreCase: true,
982 ),
983 ),
984 EqCondition(
985 op: "eq",
986 name: "field_2",
987 value: [
988 "UPPER",
989 "lower",
990 ],
991 ),
992 GlobCondition(
993 op: "glob",
994 name: "field_3",
995 value: [
996 "1.2.*",
997 "2.*",
998 ],
999 ),
1000 NotCondition(
1001 op: "not",
1002 inner: GlobCondition(
1003 op: "glob",
1004 name: "field_4",
1005 value: [
1006 "1.*",
1007 ],
1008 ),
1009 ),
1010 AndCondition(
1011 op: "and",
1012 inner: [
1013 GlobCondition(
1014 op: "glob",
1015 name: "field_5",
1016 value: [
1017 "2.*",
1018 ],
1019 ),
1020 ],
1021 ),
1022 OrCondition(
1023 op: "or",
1024 inner: [
1025 GlobCondition(
1026 op: "glob",
1027 name: "field_6",
1028 value: [
1029 "3.*",
1030 ],
1031 ),
1032 ],
1033 ),
1034 AnyCondition(
1035 op: "any",
1036 name: "obj.exceptions",
1037 inner: GlobCondition(
1038 op: "glob",
1039 name: "value",
1040 value: [
1041 "*Exception",
1042 ],
1043 ),
1044 ),
1045 AllCondition(
1046 op: "all",
1047 name: "obj.exceptions",
1048 inner: GlobCondition(
1049 op: "glob",
1050 name: "value",
1051 value: [
1052 "*Exception",
1053 ],
1054 ),
1055 ),
1056 ]
1057 "###);
1058 }
1059
1060 #[test]
1061 fn unsupported_rule_deserialize() {
1062 let bad_json = r#"{
1063 "op": "BadOperator",
1064 "name": "foo",
1065 "value": "bar"
1066 }"#;
1067
1068 let rule: RuleCondition = serde_json::from_str(bad_json).unwrap();
1069 assert!(matches!(rule, RuleCondition::Unsupported));
1070 }
1071
1072 #[test]
1073 fn test_matches() {
1075 let conditions = [
1076 (
1077 "simple",
1078 RuleCondition::glob("trace.release", "1.1.1")
1079 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1080 & RuleCondition::eq_ignore_case("trace.user.segment", "vip")
1081 & RuleCondition::eq_ignore_case("trace.transaction", "transaction1"),
1082 ),
1083 (
1084 "glob releases",
1085 RuleCondition::glob("trace.release", "1.*")
1086 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1087 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1088 ),
1089 (
1090 "glob transaction",
1091 RuleCondition::glob("trace.transaction", "trans*"),
1092 ),
1093 (
1094 "multiple releases",
1095 RuleCondition::glob("trace.release", vec!["2.1.1", "1.1.*"])
1096 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1097 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1098 ),
1099 (
1100 "multiple user segments",
1101 RuleCondition::glob("trace.release", "1.1.1")
1102 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1103 & RuleCondition::eq_ignore_case(
1104 "trace.user.segment",
1105 vec!["paid", "vip", "free"],
1106 ),
1107 ),
1108 (
1109 "multiple transactions",
1110 RuleCondition::glob("trace.transaction", &["t22", "trans*", "t33"][..]),
1111 ),
1112 (
1113 "case insensitive user segments",
1114 RuleCondition::glob("trace.release", "1.1.1")
1115 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1116 & RuleCondition::eq_ignore_case("trace.user.segment", &["ViP", "FrEe"][..]),
1117 ),
1118 (
1119 "multiple user environments",
1120 RuleCondition::glob("trace.release", "1.1.1")
1121 & RuleCondition::eq_ignore_case(
1122 "trace.environment",
1123 &["integration", "debug", "production"][..],
1124 )
1125 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1126 ),
1127 (
1128 "case insensitive environments",
1129 RuleCondition::glob("trace.release", "1.1.1")
1130 & RuleCondition::eq_ignore_case("trace.environment", &["DeBuG", "PrOd"][..])
1131 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1132 ),
1133 (
1134 "all environments",
1135 RuleCondition::glob("trace.release", "1.1.1")
1136 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1137 ),
1138 (
1139 "undefined environments",
1140 RuleCondition::glob("trace.release", "1.1.1")
1141 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1142 ),
1143 ("match no conditions", RuleCondition::all()),
1144 ("string cmp", RuleCondition::gt("trace.transaction", "t")),
1145 ];
1146
1147 let trace = mock_trace();
1148
1149 for (rule_test_name, condition) in conditions.iter() {
1150 let failure_name = format!("Failed on test: '{rule_test_name}'!!!");
1151 assert!(condition.matches(&trace), "{failure_name}");
1152 }
1153 }
1154
1155 #[test]
1156 fn test_or_combinator() {
1157 let conditions = [
1158 (
1159 "both",
1160 true,
1161 RuleCondition::eq_ignore_case("trace.environment", "debug")
1162 | RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1163 ),
1164 (
1165 "first",
1166 true,
1167 RuleCondition::eq_ignore_case("trace.environment", "debug")
1168 | RuleCondition::eq_ignore_case("trace.user.segment", "all"),
1169 ),
1170 (
1171 "second",
1172 true,
1173 RuleCondition::eq_ignore_case("trace.environment", "prod")
1174 | RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1175 ),
1176 (
1177 "none",
1178 false,
1179 RuleCondition::eq_ignore_case("trace.environment", "prod")
1180 | RuleCondition::eq_ignore_case("trace.user.segment", "all"),
1181 ),
1182 (
1183 "empty",
1184 false,
1185 RuleCondition::Or(OrCondition { inner: vec![] }),
1186 ),
1187 ("never", false, RuleCondition::never()),
1188 ];
1189
1190 let trace = mock_trace();
1191
1192 for (rule_test_name, expected, condition) in conditions.iter() {
1193 let failure_name = format!("Failed on test: '{rule_test_name}'!!!");
1194 assert!(condition.matches(&trace) == *expected, "{failure_name}");
1195 }
1196 }
1197
1198 #[test]
1199 fn test_and_combinator() {
1200 let conditions = [
1201 (
1202 "both",
1203 true,
1204 RuleCondition::eq_ignore_case("trace.environment", "debug")
1205 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1206 ),
1207 (
1208 "first",
1209 false,
1210 RuleCondition::eq_ignore_case("trace.environment", "debug")
1211 & RuleCondition::eq_ignore_case("trace.user.segment", "all"),
1212 ),
1213 (
1214 "second",
1215 false,
1216 RuleCondition::eq_ignore_case("trace.environment", "prod")
1217 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1218 ),
1219 (
1220 "none",
1221 false,
1222 RuleCondition::eq_ignore_case("trace.environment", "prod")
1223 & RuleCondition::eq_ignore_case("trace.user.segment", "all"),
1224 ),
1225 (
1226 "empty",
1227 true,
1228 RuleCondition::And(AndCondition { inner: vec![] }),
1229 ),
1230 ("all", true, RuleCondition::all()),
1231 ];
1232
1233 let trace = mock_trace();
1234
1235 for (rule_test_name, expected, condition) in conditions.iter() {
1236 let failure_name = format!("Failed on test: '{rule_test_name}'!!!");
1237 assert!(condition.matches(&trace) == *expected, "{failure_name}");
1238 }
1239 }
1240
1241 #[test]
1242 fn test_not_combinator() {
1243 let conditions = [
1244 (
1245 "not true",
1246 false,
1247 !RuleCondition::eq_ignore_case("trace.environment", "debug"),
1248 ),
1249 (
1250 "not false",
1251 true,
1252 !RuleCondition::eq_ignore_case("trace.environment", "prod"),
1253 ),
1254 ];
1255
1256 let trace = mock_trace();
1257
1258 for (rule_test_name, expected, condition) in conditions.iter() {
1259 let failure_name = format!("Failed on test: '{rule_test_name}'!!!");
1260 assert!(condition.matches(&trace) == *expected, "{failure_name}");
1261 }
1262 }
1263
1264 #[test]
1265 fn test_does_not_match() {
1267 let conditions = [
1268 (
1269 "release",
1270 RuleCondition::glob("trace.release", "1.1.2")
1271 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1272 & RuleCondition::eq_ignore_case("trace.user", "vip"),
1273 ),
1274 (
1275 "user segment",
1276 RuleCondition::glob("trace.release", "1.1.1")
1277 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1278 & RuleCondition::eq_ignore_case("trace.user", "all"),
1279 ),
1280 (
1281 "environment",
1282 RuleCondition::glob("trace.release", "1.1.1")
1283 & RuleCondition::eq_ignore_case("trace.environment", "prod")
1284 & RuleCondition::eq_ignore_case("trace.user", "vip"),
1285 ),
1286 (
1287 "transaction",
1288 RuleCondition::glob("trace.release", "1.1.1")
1289 & RuleCondition::glob("trace.transaction", "t22")
1290 & RuleCondition::eq_ignore_case("trace.user", "vip"),
1291 ),
1292 ];
1293
1294 let trace = mock_trace();
1295
1296 for (rule_test_name, condition) in conditions.iter() {
1297 let failure_name = format!("Failed on test: '{rule_test_name}'!!!");
1298 assert!(!condition.matches(&trace), "{failure_name}");
1299 }
1300 }
1301
1302 #[test]
1303 fn test_any_condition_with_match() {
1304 let condition = RuleCondition::for_any(
1305 "trace.exceptions",
1306 RuleCondition::glob("name", "*Exception"),
1307 );
1308
1309 let trace = mock_trace();
1310
1311 assert!(condition.matches(&trace));
1312 }
1313
1314 #[test]
1315 fn test_any_condition_with_no_match() {
1316 let condition =
1317 RuleCondition::for_any("trace.exceptions", RuleCondition::glob("name", "Error"));
1318
1319 let trace = mock_trace();
1320
1321 assert!(!condition.matches(&trace));
1322 }
1323
1324 #[test]
1325 fn test_all_condition() {
1326 let condition =
1327 RuleCondition::for_all("trace.exceptions", RuleCondition::glob("name", "Null*"));
1328
1329 let trace = mock_trace();
1330
1331 assert!(condition.matches(&trace));
1332 }
1333
1334 #[test]
1335 fn test_all_condition_with_no_match() {
1336 let condition = RuleCondition::for_all(
1337 "trace.exceptions",
1338 RuleCondition::glob("name", "*Exception"),
1339 );
1340
1341 let trace = mock_trace();
1342
1343 assert!(!condition.matches(&trace));
1344 }
1345}