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(val)) => self.cmp(f, val),
83 (Some(Val::String(f)), Value::Array(arr)) => arr
84 .iter()
85 .filter_map(|v| v.as_str())
86 .any(|v| self.cmp(v, f)),
87 (Some(Val::HexId(f)), Value::String(val)) => f.match_str(val),
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 uuid::Uuid;
841
842 use super::*;
843 use crate::{GetterIter, HexId};
844
845 #[derive(Debug)]
846 struct Exception {
847 name: String,
848 }
849
850 impl Getter for Exception {
851 fn get_value(&self, path: &str) -> Option<Val<'_>> {
852 Some(match path {
853 "name" => self.name.as_str().into(),
854 _ => return None,
855 })
856 }
857 }
858
859 struct Trace {
860 trace_id: Uuid,
861 span_id: [u8; 4],
862 transaction: String,
863 release: String,
864 environment: String,
865 user_segment: String,
866 exceptions: Vec<Exception>,
867 }
868
869 impl Getter for Trace {
870 fn get_value(&self, path: &str) -> Option<Val<'_>> {
871 Some(match path.strip_prefix("trace.")? {
872 "trace_id" => (&self.trace_id).into(),
873 "span_id" => Val::HexId(HexId(&self.span_id[..])),
874 "transaction" => self.transaction.as_str().into(),
875 "release" => self.release.as_str().into(),
876 "environment" => self.environment.as_str().into(),
877 "user.segment" => self.user_segment.as_str().into(),
878 _ => {
879 return None;
880 }
881 })
882 }
883
884 fn get_iter(&self, path: &str) -> Option<GetterIter<'_>> {
885 Some(match path.strip_prefix("trace.")? {
886 "exceptions" => GetterIter::new(self.exceptions.iter()),
887 _ => return None,
888 })
889 }
890 }
891
892 fn mock_trace() -> Trace {
893 Trace {
894 trace_id: "6b7d15b8-cee2-4354-9fee-dae7ef43e434".parse().unwrap(),
895 span_id: [0xde, 0xad, 0xbe, 0xef],
896 transaction: "transaction1".to_string(),
897 release: "1.1.1".to_string(),
898 environment: "debug".to_string(),
899 user_segment: "vip".to_string(),
900 exceptions: vec![
901 Exception {
902 name: "NullPointerException".to_string(),
903 },
904 Exception {
905 name: "NullUser".to_string(),
906 },
907 ],
908 }
909 }
910
911 #[test]
912 fn deserialize() {
913 let serialized_rules = r#"[
914 {
915 "op":"eq",
916 "name": "field_1",
917 "value": ["UPPER","lower"],
918 "options":{
919 "ignoreCase": true
920 }
921 },
922 {
923 "op":"eq",
924 "name": "field_2",
925 "value": ["UPPER","lower"]
926 },
927 {
928 "op":"glob",
929 "name": "field_3",
930 "value": ["1.2.*","2.*"]
931 },
932 {
933 "op":"not",
934 "inner": {
935 "op":"glob",
936 "name": "field_4",
937 "value": ["1.*"]
938 }
939 },
940 {
941 "op":"and",
942 "inner": [{
943 "op":"glob",
944 "name": "field_5",
945 "value": ["2.*"]
946 }]
947 },
948 {
949 "op":"or",
950 "inner": [{
951 "op":"glob",
952 "name": "field_6",
953 "value": ["3.*"]
954 }]
955 },
956 {
957 "op": "any",
958 "name": "obj.exceptions",
959 "inner": {
960 "op": "glob",
961 "name": "value",
962 "value": ["*Exception"]
963 }
964 },
965 {
966 "op": "all",
967 "name": "obj.exceptions",
968 "inner": {
969 "op": "glob",
970 "name": "value",
971 "value": ["*Exception"]
972 }
973 }
974 ]"#;
975
976 let rules: Result<Vec<RuleCondition>, _> = serde_json::from_str(serialized_rules);
977 assert!(rules.is_ok());
978 let rules = rules.unwrap();
979 insta::assert_ron_snapshot!(rules, @r###"
980 [
981 EqCondition(
982 op: "eq",
983 name: "field_1",
984 value: [
985 "UPPER",
986 "lower",
987 ],
988 options: EqCondOptions(
989 ignoreCase: true,
990 ),
991 ),
992 EqCondition(
993 op: "eq",
994 name: "field_2",
995 value: [
996 "UPPER",
997 "lower",
998 ],
999 ),
1000 GlobCondition(
1001 op: "glob",
1002 name: "field_3",
1003 value: [
1004 "1.2.*",
1005 "2.*",
1006 ],
1007 ),
1008 NotCondition(
1009 op: "not",
1010 inner: GlobCondition(
1011 op: "glob",
1012 name: "field_4",
1013 value: [
1014 "1.*",
1015 ],
1016 ),
1017 ),
1018 AndCondition(
1019 op: "and",
1020 inner: [
1021 GlobCondition(
1022 op: "glob",
1023 name: "field_5",
1024 value: [
1025 "2.*",
1026 ],
1027 ),
1028 ],
1029 ),
1030 OrCondition(
1031 op: "or",
1032 inner: [
1033 GlobCondition(
1034 op: "glob",
1035 name: "field_6",
1036 value: [
1037 "3.*",
1038 ],
1039 ),
1040 ],
1041 ),
1042 AnyCondition(
1043 op: "any",
1044 name: "obj.exceptions",
1045 inner: GlobCondition(
1046 op: "glob",
1047 name: "value",
1048 value: [
1049 "*Exception",
1050 ],
1051 ),
1052 ),
1053 AllCondition(
1054 op: "all",
1055 name: "obj.exceptions",
1056 inner: GlobCondition(
1057 op: "glob",
1058 name: "value",
1059 value: [
1060 "*Exception",
1061 ],
1062 ),
1063 ),
1064 ]
1065 "###);
1066 }
1067
1068 #[test]
1069 fn unsupported_rule_deserialize() {
1070 let bad_json = r#"{
1071 "op": "BadOperator",
1072 "name": "foo",
1073 "value": "bar"
1074 }"#;
1075
1076 let rule: RuleCondition = serde_json::from_str(bad_json).unwrap();
1077 assert!(matches!(rule, RuleCondition::Unsupported));
1078 }
1079
1080 #[test]
1081 fn test_matches() {
1083 let conditions = [
1084 (
1085 "simple",
1086 RuleCondition::glob("trace.release", "1.1.1")
1087 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1088 & RuleCondition::eq_ignore_case("trace.user.segment", "vip")
1089 & RuleCondition::eq_ignore_case("trace.transaction", "transaction1"),
1090 ),
1091 (
1092 "glob releases",
1093 RuleCondition::glob("trace.release", "1.*")
1094 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1095 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1096 ),
1097 (
1098 "glob transaction",
1099 RuleCondition::glob("trace.transaction", "trans*"),
1100 ),
1101 (
1102 "multiple releases",
1103 RuleCondition::glob("trace.release", vec!["2.1.1", "1.1.*"])
1104 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1105 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1106 ),
1107 (
1108 "multiple user segments",
1109 RuleCondition::glob("trace.release", "1.1.1")
1110 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1111 & RuleCondition::eq_ignore_case(
1112 "trace.user.segment",
1113 vec!["paid", "vip", "free"],
1114 ),
1115 ),
1116 (
1117 "multiple transactions",
1118 RuleCondition::glob("trace.transaction", &["t22", "trans*", "t33"][..]),
1119 ),
1120 (
1121 "case insensitive user segments",
1122 RuleCondition::glob("trace.release", "1.1.1")
1123 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1124 & RuleCondition::eq_ignore_case("trace.user.segment", &["ViP", "FrEe"][..]),
1125 ),
1126 (
1127 "multiple user environments",
1128 RuleCondition::glob("trace.release", "1.1.1")
1129 & RuleCondition::eq_ignore_case(
1130 "trace.environment",
1131 &["integration", "debug", "production"][..],
1132 )
1133 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1134 ),
1135 (
1136 "case insensitive environments",
1137 RuleCondition::glob("trace.release", "1.1.1")
1138 & RuleCondition::eq_ignore_case("trace.environment", &["DeBuG", "PrOd"][..])
1139 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1140 ),
1141 (
1142 "all environments",
1143 RuleCondition::glob("trace.release", "1.1.1")
1144 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1145 ),
1146 (
1147 "undefined environments",
1148 RuleCondition::glob("trace.release", "1.1.1")
1149 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1150 ),
1151 (
1152 "trace/span ID bytes",
1153 RuleCondition::eq("trace.trace_id", "6b7d15b8cee243549feedae7ef43e434")
1154 & RuleCondition::eq("trace.span_id", "DEADBEEF"),
1155 ),
1156 ("match no conditions", RuleCondition::all()),
1157 ("string cmp", RuleCondition::gt("trace.transaction", "t")),
1158 ];
1159
1160 let trace = mock_trace();
1161
1162 for (rule_test_name, condition) in conditions.iter() {
1163 let failure_name = format!("Failed on test: '{rule_test_name}'!!!");
1164 assert!(condition.matches(&trace), "{failure_name}");
1165 }
1166 }
1167
1168 #[test]
1169 fn test_or_combinator() {
1170 let conditions = [
1171 (
1172 "both",
1173 true,
1174 RuleCondition::eq_ignore_case("trace.environment", "debug")
1175 | RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1176 ),
1177 (
1178 "first",
1179 true,
1180 RuleCondition::eq_ignore_case("trace.environment", "debug")
1181 | RuleCondition::eq_ignore_case("trace.user.segment", "all"),
1182 ),
1183 (
1184 "second",
1185 true,
1186 RuleCondition::eq_ignore_case("trace.environment", "prod")
1187 | RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1188 ),
1189 (
1190 "none",
1191 false,
1192 RuleCondition::eq_ignore_case("trace.environment", "prod")
1193 | RuleCondition::eq_ignore_case("trace.user.segment", "all"),
1194 ),
1195 (
1196 "empty",
1197 false,
1198 RuleCondition::Or(OrCondition { inner: vec![] }),
1199 ),
1200 ("never", false, RuleCondition::never()),
1201 ];
1202
1203 let trace = mock_trace();
1204
1205 for (rule_test_name, expected, condition) in conditions.iter() {
1206 let failure_name = format!("Failed on test: '{rule_test_name}'!!!");
1207 assert!(condition.matches(&trace) == *expected, "{failure_name}");
1208 }
1209 }
1210
1211 #[test]
1212 fn test_and_combinator() {
1213 let conditions = [
1214 (
1215 "both",
1216 true,
1217 RuleCondition::eq_ignore_case("trace.environment", "debug")
1218 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1219 ),
1220 (
1221 "first",
1222 false,
1223 RuleCondition::eq_ignore_case("trace.environment", "debug")
1224 & RuleCondition::eq_ignore_case("trace.user.segment", "all"),
1225 ),
1226 (
1227 "second",
1228 false,
1229 RuleCondition::eq_ignore_case("trace.environment", "prod")
1230 & RuleCondition::eq_ignore_case("trace.user.segment", "vip"),
1231 ),
1232 (
1233 "none",
1234 false,
1235 RuleCondition::eq_ignore_case("trace.environment", "prod")
1236 & RuleCondition::eq_ignore_case("trace.user.segment", "all"),
1237 ),
1238 (
1239 "empty",
1240 true,
1241 RuleCondition::And(AndCondition { inner: vec![] }),
1242 ),
1243 ("all", true, RuleCondition::all()),
1244 ];
1245
1246 let trace = mock_trace();
1247
1248 for (rule_test_name, expected, condition) in conditions.iter() {
1249 let failure_name = format!("Failed on test: '{rule_test_name}'!!!");
1250 assert!(condition.matches(&trace) == *expected, "{failure_name}");
1251 }
1252 }
1253
1254 #[test]
1255 fn test_not_combinator() {
1256 let conditions = [
1257 (
1258 "not true",
1259 false,
1260 !RuleCondition::eq_ignore_case("trace.environment", "debug"),
1261 ),
1262 (
1263 "not false",
1264 true,
1265 !RuleCondition::eq_ignore_case("trace.environment", "prod"),
1266 ),
1267 ];
1268
1269 let trace = mock_trace();
1270
1271 for (rule_test_name, expected, condition) in conditions.iter() {
1272 let failure_name = format!("Failed on test: '{rule_test_name}'!!!");
1273 assert!(condition.matches(&trace) == *expected, "{failure_name}");
1274 }
1275 }
1276
1277 #[test]
1278 fn test_does_not_match() {
1280 let conditions = [
1281 (
1282 "release",
1283 RuleCondition::glob("trace.release", "1.1.2")
1284 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1285 & RuleCondition::eq_ignore_case("trace.user", "vip"),
1286 ),
1287 (
1288 "user segment",
1289 RuleCondition::glob("trace.release", "1.1.1")
1290 & RuleCondition::eq_ignore_case("trace.environment", "debug")
1291 & RuleCondition::eq_ignore_case("trace.user", "all"),
1292 ),
1293 (
1294 "environment",
1295 RuleCondition::glob("trace.release", "1.1.1")
1296 & RuleCondition::eq_ignore_case("trace.environment", "prod")
1297 & RuleCondition::eq_ignore_case("trace.user", "vip"),
1298 ),
1299 (
1300 "transaction",
1301 RuleCondition::glob("trace.release", "1.1.1")
1302 & RuleCondition::glob("trace.transaction", "t22")
1303 & RuleCondition::eq_ignore_case("trace.user", "vip"),
1304 ),
1305 ("span ID", RuleCondition::eq("trace.span_id", "deadbeer")),
1306 ];
1307
1308 let trace = mock_trace();
1309
1310 for (rule_test_name, condition) in conditions.iter() {
1311 let failure_name = format!("Failed on test: '{rule_test_name}'!!!");
1312 assert!(!condition.matches(&trace), "{failure_name}");
1313 }
1314 }
1315
1316 #[test]
1317 fn test_any_condition_with_match() {
1318 let condition = RuleCondition::for_any(
1319 "trace.exceptions",
1320 RuleCondition::glob("name", "*Exception"),
1321 );
1322
1323 let trace = mock_trace();
1324
1325 assert!(condition.matches(&trace));
1326 }
1327
1328 #[test]
1329 fn test_any_condition_with_no_match() {
1330 let condition =
1331 RuleCondition::for_any("trace.exceptions", RuleCondition::glob("name", "Error"));
1332
1333 let trace = mock_trace();
1334
1335 assert!(!condition.matches(&trace));
1336 }
1337
1338 #[test]
1339 fn test_all_condition() {
1340 let condition =
1341 RuleCondition::for_all("trace.exceptions", RuleCondition::glob("name", "Null*"));
1342
1343 let trace = mock_trace();
1344
1345 assert!(condition.matches(&trace));
1346 }
1347
1348 #[test]
1349 fn test_all_condition_with_no_match() {
1350 let condition = RuleCondition::for_all(
1351 "trace.exceptions",
1352 RuleCondition::glob("name", "*Exception"),
1353 );
1354
1355 let trace = mock_trace();
1356
1357 assert!(!condition.matches(&trace));
1358 }
1359}