1use chrono::{DateTime, Utc};
2use regex::Regex;
3use serde::Deserialize;
4use serde::Serialize;
5use std::collections::HashMap;
6use std::fmt;
7use tracing::error;
8
9use crate::config::ComponentDataManipulationRule;
10use crate::config::ComponentEventFilteringRuleCondition;
11use crate::config::DataCollectionComponents;
12
13pub type Dict = HashMap<String, serde_json::Value>;
14
15#[derive(Serialize, Debug, Clone, Default)]
16pub struct Event {
17 pub uuid: String,
18 pub timestamp: DateTime<Utc>,
19 #[serde(rename = "type")]
20 pub event_type: EventType,
21 pub data: Data,
22 pub context: Context,
23 #[serde(skip_serializing)]
24 pub components: Option<HashMap<String, bool>>,
25 pub from: Option<String>,
26 pub consent: Option<Consent>,
27}
28
29impl<'de> Deserialize<'de> for Event {
30 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
31 where
32 D: serde::Deserializer<'de>,
33 {
34 #[derive(Deserialize)]
35 struct EventHelper {
36 uuid: String,
37 timestamp: DateTime<Utc>,
38 #[serde(rename = "type")]
39 event_type: EventType,
40 #[serde(default)]
41 data: serde_json::Value,
42 #[serde(default)]
43 context: Context,
44 #[serde(default)]
45 components: Option<HashMap<String, bool>>,
46 #[serde(default)]
47 from: Option<String>,
48 #[serde(default)]
49 consent: Option<Consent>,
50 }
51
52 let helper = EventHelper::deserialize(deserializer)?;
53 let data = match helper.event_type {
54 EventType::Page => Data::Page(serde_json::from_value(helper.data).unwrap()),
55 EventType::User => Data::User(serde_json::from_value(helper.data).unwrap()),
56 EventType::Track => {
57 let mut track_data: Track = serde_json::from_value(helper.data).unwrap();
58
59 if let Some(serde_json::Value::Array(products_array)) =
61 track_data.properties.remove("products")
62 {
63 track_data.products = products_array
64 .into_iter()
65 .filter_map(|p| {
66 if let serde_json::Value::Object(map) = p {
67 Some(map.into_iter().collect())
68 } else {
69 None
70 }
71 })
72 .collect();
73 }
74
75 Data::Track(track_data)
76 }
77 };
78
79 Ok(Event {
80 uuid: helper.uuid,
81 timestamp: helper.timestamp,
82 event_type: helper.event_type,
83 data,
84 context: helper.context,
85 components: helper.components,
86 from: helper.from,
87 consent: helper.consent,
88 })
89 }
90}
91
92impl Event {
93 pub fn is_component_enabled(&self, config: &DataCollectionComponents) -> &bool {
94 if self.components.is_none() {
96 return &true;
97 }
98
99 let components = self.components.as_ref().unwrap();
100
101 let all = components.get("all").unwrap_or(&true);
103
104 for key in [&config.id, &config.project_component_id, &config.slug] {
106 if let Some(enabled) = components.get(key.as_str()) {
107 return enabled;
108 }
109 }
110
111 all
112 }
113 pub fn should_filter_out(&self, condition: &ComponentEventFilteringRuleCondition) -> bool {
114 let query = condition.field.as_str();
115 let operator = condition.operator.as_str();
116 let value = condition.value.as_str();
117
118 let re = Regex::new(r"data\.(page|track|user)\.properties\.([a-zA-Z0-9_]+)").unwrap();
119 if let Some(captures) = re.captures(query) {
120 let data_type = captures.get(1).unwrap().as_str();
121 let custom_field = captures.get(2).unwrap().as_str();
122
123 match data_type {
124 "page" => {
125 if let Data::Page(data) = &self.data {
126 if let Some(found_customer_property_value) =
127 data.properties.get(custom_field)
128 {
129 return evaluate_string_filter(
130 found_customer_property_value.to_string().as_str(),
131 operator,
132 value,
133 );
134 }
135 }
136 }
137 "track" => {
138 if let Data::Track(data) = &self.data {
139 if let Some(found_customer_property_value) =
140 data.properties.get(custom_field)
141 {
142 return evaluate_string_filter(
143 found_customer_property_value.to_string().as_str(),
144 operator,
145 value,
146 );
147 }
148 }
149 }
150 "user" => {
151 if let Data::User(data) = &self.data {
152 if let Some(found_customer_property_value) =
153 data.properties.get(custom_field)
154 {
155 return evaluate_string_filter(
156 found_customer_property_value.to_string().as_str(),
157 operator,
158 value,
159 );
160 }
161 }
162 }
163 _ => {}
164 }
165 }
166
167 match query {
168 "uuid" => evaluate_string_filter(&self.uuid, operator, value),
169 "timestamp" => {
170 let timestamp_f64 = self.timestamp.timestamp() as f64;
171 let value_f64 = value.parse::<f64>().unwrap_or_default();
172 evaluate_number_filter(×tamp_f64, operator, &value_f64)
173 }
174 "timestamp-millis" => {
175 let timestamp_f64 = self.timestamp.timestamp_millis() as f64;
176 let value_f64 = value.parse::<f64>().unwrap_or_default();
177 evaluate_number_filter(×tamp_f64, operator, &value_f64)
178 }
179 "timestamp-micros" => {
180 let timestamp_f64 = self.timestamp.timestamp_micros() as f64;
181 let value_f64 = value.parse::<f64>().unwrap_or_default();
182 evaluate_number_filter(×tamp_f64, operator, &value_f64)
183 }
184 "event-type" => {
185 let event_type_str = match self.event_type {
186 EventType::Page => "page",
187 EventType::User => "user",
188 EventType::Track => "track",
189 };
190 evaluate_string_filter(event_type_str, operator, value)
191 }
192 "consent" => {
193 let consent_str = self.consent.as_ref().map_or("", |c| match c {
194 Consent::Granted => "granted",
195 Consent::Denied => "denied",
196 Consent::Pending => "pending",
197 });
198 evaluate_string_filter(consent_str, operator, value)
199 }
200 "context.client.ip" => evaluate_string_filter(&self.context.client.ip, operator, value),
202 "context.client.proxy-type" => {
203 let proxy_type_str = self.context.client.proxy_type.as_deref().unwrap_or("");
204 evaluate_string_filter(proxy_type_str, operator, value)
205 }
206 "context.client.proxy-desc" => {
207 let proxy_desc_str = self.context.client.proxy_desc.as_deref().unwrap_or("");
208 evaluate_string_filter(proxy_desc_str, operator, value)
209 }
210 "context.client.as-name" => {
211 let as_name_str = self.context.client.as_name.as_deref().unwrap_or("");
212 evaluate_string_filter(as_name_str, operator, value)
213 }
214 "context.client.as-number" => {
215 let as_number_f64 = self.context.client.as_number.unwrap_or(0) as f64;
216 let value_f64 = value.parse::<f64>().unwrap_or_default();
217 evaluate_number_filter(&as_number_f64, operator, &value_f64)
218 }
219 "context.client.locale" => {
220 evaluate_string_filter(&self.context.client.locale, operator, value)
221 }
222 "context.client.accept-language" => {
223 evaluate_string_filter(&self.context.client.accept_language, operator, value)
224 }
225 "context.client.timezone" => {
226 evaluate_string_filter(&self.context.client.timezone, operator, value)
227 }
228 "context.client.user-agent" => {
229 evaluate_string_filter(&self.context.client.user_agent, operator, value)
230 }
231 "context.client.user-agent-version-list" => evaluate_string_filter(
232 &self.context.client.user_agent_version_list,
233 operator,
234 value,
235 ),
236 "context.client.user-agent-mobile" => {
237 evaluate_string_filter(&self.context.client.user_agent_mobile, operator, value)
238 }
239 "context.client.os-name" => {
240 evaluate_string_filter(&self.context.client.os_name, operator, value)
241 }
242 "context.client.user-agent-architecture" => evaluate_string_filter(
243 &self.context.client.user_agent_architecture,
244 operator,
245 value,
246 ),
247 "context.client.user-agent-bitness" => {
248 evaluate_string_filter(&self.context.client.user_agent_bitness, operator, value)
249 }
250 "context.client.user-agent-full-version-list" => evaluate_string_filter(
251 &self.context.client.user_agent_full_version_list,
252 operator,
253 value,
254 ),
255 "context.client.user-agent-model" => {
256 evaluate_string_filter(&self.context.client.user_agent_model, operator, value)
257 }
258 "context.client.os-version" => {
259 evaluate_string_filter(&self.context.client.os_version, operator, value)
260 }
261 "context.client.screen-width" => {
262 let width_f64 = self.context.client.screen_width as f64;
263 let value_f64 = value.parse::<f64>().unwrap_or_default();
264 evaluate_number_filter(&width_f64, operator, &value_f64)
265 }
266 "context.client.screen-height" => {
267 let height_f64 = self.context.client.screen_height as f64;
268 let value_f64 = value.parse::<f64>().unwrap_or_default();
269 evaluate_number_filter(&height_f64, operator, &value_f64)
270 }
271 "context.client.screen-density" => {
272 let density_f64 = self.context.client.screen_density as f64;
273 let value_f64 = value.parse::<f64>().unwrap_or_default();
274 evaluate_number_filter(&density_f64, operator, &value_f64)
275 }
276 "context.client.continent" => {
277 evaluate_string_filter(&self.context.client.continent, operator, value)
278 }
279 "context.client.country-code" => {
280 evaluate_string_filter(&self.context.client.country_code, operator, value)
281 }
282 "context.client.country-name" => {
283 evaluate_string_filter(&self.context.client.country_name, operator, value)
284 }
285 "context.client.region" => {
286 evaluate_string_filter(&self.context.client.region, operator, value)
287 }
288 "context.client.city" => {
289 evaluate_string_filter(&self.context.client.city, operator, value)
290 }
291
292 "context.session.session-id" => {
294 evaluate_string_filter(&self.context.session.session_id, operator, value)
295 }
296 "context.session.previous-session-id" => {
297 evaluate_string_filter(&self.context.session.previous_session_id, operator, value)
298 }
299 "context.session.session-count" => {
300 let count_f64 = self.context.session.session_count as f64;
301 let value_f64 = value.parse::<f64>().unwrap_or_default();
302 evaluate_number_filter(&count_f64, operator, &value_f64)
303 }
304 "context.session.session-start" => evaluate_boolean_filter(
305 self.context.session.session_start,
306 operator,
307 value == "true",
308 ),
309 "context.session.first-seen" => {
310 let timestamp_f64 = self.context.session.first_seen.timestamp() as f64;
311 let value_f64 = value.parse::<f64>().unwrap_or_default();
312 evaluate_number_filter(×tamp_f64, operator, &value_f64)
313 }
314 "context.session.last-seen" => {
315 let timestamp_f64 = self.context.session.last_seen.timestamp() as f64;
316 let value_f64 = value.parse::<f64>().unwrap_or_default();
317 evaluate_number_filter(×tamp_f64, operator, &value_f64)
318 }
319
320 "context.campaign.name" => {
322 evaluate_string_filter(&self.context.campaign.name, operator, value)
323 }
324 "context.campaign.source" => {
325 evaluate_string_filter(&self.context.campaign.source, operator, value)
326 }
327 "context.campaign.medium" => {
328 evaluate_string_filter(&self.context.campaign.medium, operator, value)
329 }
330 "context.campaign.term" => {
331 evaluate_string_filter(&self.context.campaign.term, operator, value)
332 }
333 "context.campaign.content" => {
334 evaluate_string_filter(&self.context.campaign.content, operator, value)
335 }
336 "context.campaign.creative-format" => {
337 evaluate_string_filter(&self.context.campaign.creative_format, operator, value)
338 }
339 "context.campaign.marketing-tactic" => {
340 evaluate_string_filter(&self.context.campaign.marketing_tactic, operator, value)
341 }
342
343 "data.page.name" => {
345 if let Data::Page(ref data) = self.data {
346 evaluate_string_filter(&data.name, operator, value)
347 } else {
348 false
349 }
350 }
351 "data.page.category" => {
352 if let Data::Page(ref data) = self.data {
353 evaluate_string_filter(&data.category, operator, value)
354 } else {
355 false
356 }
357 }
358 "data.page.title" => {
359 if let Data::Page(ref data) = self.data {
360 evaluate_string_filter(&data.title, operator, value)
361 } else {
362 false
363 }
364 }
365 "data.page.url" => {
366 if let Data::Page(ref data) = self.data {
367 evaluate_string_filter(&data.url, operator, value)
368 } else {
369 false
370 }
371 }
372 "data.page.path" => {
373 if let Data::Page(ref data) = self.data {
374 evaluate_string_filter(&data.path, operator, value)
375 } else {
376 false
377 }
378 }
379 "data.page.search" => {
380 if let Data::Page(ref data) = self.data {
381 evaluate_string_filter(&data.search, operator, value)
382 } else {
383 false
384 }
385 }
386 "data.page.referrer" => {
387 if let Data::Page(ref data) = self.data {
388 evaluate_string_filter(&data.referrer, operator, value)
389 } else {
390 false
391 }
392 }
393
394 "data.track.name" => {
396 if let Data::Track(ref data) = self.data {
397 evaluate_string_filter(&data.name, operator, value)
398 } else {
399 false
400 }
401 }
402
403 "data.user.user-id" => {
405 if let Data::User(ref data) = self.data {
406 evaluate_string_filter(&data.user_id, operator, value)
407 } else {
408 false
409 }
410 }
411 "data.user.anonymous-id" => {
412 if let Data::User(ref data) = self.data {
413 evaluate_string_filter(&data.anonymous_id, operator, value)
414 } else {
415 false
416 }
417 }
418 "data.user.edgee-id" => {
419 if let Data::User(ref data) = self.data {
420 evaluate_string_filter(&data.edgee_id, operator, value)
421 } else {
422 false
423 }
424 }
425 _ => false,
426 }
427 }
428 pub fn apply_data_manipulation_rules(&mut self, rules: &[ComponentDataManipulationRule]) {
429 rules.iter().for_each(|rule| {
430 rule.event_types
431 .iter()
432 .for_each(|event_type| match event_type.as_str() {
433 "page" => {
434 if let Data::Page(ref mut data) = self.data {
435 rule.manipulations.iter().for_each(|manipulation| {
436 let value = data.properties.get(&manipulation.from_property);
437 if let Some(value) = value {
438 data.properties
439 .insert(manipulation.to_property.clone(), value.clone());
440 data.properties.remove(&manipulation.from_property);
441 }
442 });
443 }
444 }
445 "track" => {
446 if let Data::Track(ref mut data) = self.data {
447 rule.manipulations.iter().for_each(|manipulation| {
448 if manipulation.manipulation_type == "replace-event-name" {
449 if data.name == manipulation.from_property {
450 data.name = manipulation.to_property.clone();
451 }
452 } else {
453 let value = data.properties.get(&manipulation.from_property);
454 if let Some(value) = value {
455 data.properties.insert(
456 manipulation.to_property.clone(),
457 value.clone(),
458 );
459 data.properties.remove(&manipulation.from_property);
460 }
461 }
462 });
463 }
464 }
465 "user" => {
466 if let Data::User(ref mut data) = self.data {
467 rule.manipulations.iter().for_each(|manipulation| {
468 let value = data.properties.get(&manipulation.from_property);
469 if let Some(value) = value {
470 data.properties
471 .insert(manipulation.to_property.clone(), value.clone());
472 data.properties.remove(&manipulation.from_property);
473 }
474 });
475 }
476 }
477 _ => {}
478 });
479 });
480 }
481}
482
483pub fn evaluate_boolean_filter(field_value: bool, operator: &str, condition_value: bool) -> bool {
484 match operator {
485 "eq" => field_value == condition_value,
486 "neq" => field_value != condition_value,
487 _ => {
488 error!("Invalid operator: {}", operator);
489 false
490 }
491 }
492}
493
494pub fn evaluate_string_filter(field_value: &str, operator: &str, condition_value: &str) -> bool {
495 match operator {
496 "eq" => field_value == condition_value,
497 "neq" => field_value != condition_value,
498 "in" => condition_value.split(',').any(|v| v.trim() == field_value),
499 "nin" => !condition_value.split(',').any(|v| v.trim() == field_value),
500 "is_null" => field_value.is_empty(),
501 "is_not_null" => !field_value.is_empty(),
502 "sw" => field_value.starts_with(condition_value),
503 "nsw" => !field_value.starts_with(condition_value),
504 _ => {
505 error!("Invalid operator: {}", operator);
506 false
507 }
508 }
509}
510
511pub fn evaluate_number_filter(field_value: &f64, operator: &str, condition_value: &f64) -> bool {
512 match operator {
513 "eq" => field_value == condition_value,
514 "neq" => field_value != condition_value,
515 "gt" => field_value > condition_value,
516 "lt" => field_value < condition_value,
517 "gte" => field_value >= condition_value,
518 "lte" => field_value <= condition_value,
519 _ => {
520 error!("Invalid operator: {}", operator);
521 false
522 }
523 }
524}
525
526#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
527pub enum EventType {
528 #[serde(rename = "page")]
529 #[default]
530 Page,
531 #[serde(rename = "user")]
532 User,
533 #[serde(rename = "track")]
534 Track,
535}
536
537impl fmt::Display for EventType {
538 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
539 match self {
540 EventType::Page => write!(f, "page"),
541 EventType::User => write!(f, "user"),
542 EventType::Track => write!(f, "track"),
543 }
544 }
545}
546
547#[derive(Serialize, Deserialize, Debug, Clone)]
548#[serde(untagged)]
549pub enum Data {
550 Page(Page),
551 User(User),
552 Track(Track),
553}
554
555impl Default for Data {
556 fn default() -> Self {
557 Data::Page(Page::default())
558 }
559}
560
561#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
562pub enum Consent {
563 #[serde(rename = "pending")]
564 Pending,
565 #[serde(rename = "granted")]
566 Granted,
567 #[serde(rename = "denied")]
568 Denied,
569}
570
571impl fmt::Display for Consent {
572 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
573 match self {
574 Consent::Pending => write!(f, "pending"),
575 Consent::Granted => write!(f, "granted"),
576 Consent::Denied => write!(f, "denied"),
577 }
578 }
579}
580
581#[derive(Serialize, Deserialize, Debug, Default, Clone)]
582pub struct Page {
583 #[serde(default, skip_serializing_if = "String::is_empty")]
585 pub name: String,
586 #[serde(default, skip_serializing_if = "String::is_empty")]
587 pub category: String,
588 #[serde(default, skip_serializing_if = "Vec::is_empty")]
589 pub keywords: Vec<String>,
590 pub title: String,
591 pub url: String,
592 pub path: String,
593 #[serde(default, skip_serializing_if = "String::is_empty")]
594 pub search: String,
595 #[serde(default, skip_serializing_if = "String::is_empty")]
596 pub referrer: String,
597 #[serde(default)]
598 pub properties: Dict, }
600
601#[derive(Serialize, Deserialize, Debug, Default, Clone)]
602pub struct User {
603 #[serde(default, skip_serializing_if = "String::is_empty")]
604 pub user_id: String,
605 #[serde(default, skip_serializing_if = "String::is_empty")]
606 pub anonymous_id: String,
607 pub edgee_id: String,
608 #[serde(default)]
609 pub properties: Dict, #[serde(skip_serializing)]
611 pub native_cookie_ids: Option<HashMap<String, String>>,
612}
613
614#[derive(Serialize, Deserialize, Debug, Default, Clone)]
615pub struct Track {
616 #[serde(default)]
617 pub name: String,
618 #[serde(default)]
619 pub properties: Dict, #[serde(default, skip_serializing_if = "Vec::is_empty")]
621 pub products: Vec<Dict>,
622}
623
624#[derive(Serialize, Deserialize, Debug, Default, Clone)]
625pub struct Context {
626 #[serde(default)]
627 pub page: Page,
628 pub user: User,
629 pub client: Client,
630 #[serde(default)]
631 pub campaign: Campaign,
632 pub session: Session,
633}
634
635#[allow(dead_code)]
636#[derive(Serialize, Deserialize, Debug, Default, Clone)]
637pub struct Campaign {
638 #[serde(default, skip_serializing_if = "String::is_empty")]
639 pub name: String,
640 #[serde(default, skip_serializing_if = "String::is_empty")]
641 pub source: String,
642 #[serde(default, skip_serializing_if = "String::is_empty")]
643 pub medium: String,
644 #[serde(default, skip_serializing_if = "String::is_empty")]
645 pub term: String,
646 #[serde(default, skip_serializing_if = "String::is_empty")]
647 pub content: String,
648 #[serde(default, skip_serializing_if = "String::is_empty")]
649 pub creative_format: String,
650 #[serde(default, skip_serializing_if = "String::is_empty")]
651 pub marketing_tactic: String,
652}
653
654#[allow(dead_code)]
655#[derive(Serialize, Deserialize, Debug, Default, Clone)]
656pub struct Client {
657 pub ip: String,
658 pub proxy_type: Option<String>,
659 pub proxy_desc: Option<String>,
660 pub as_name: Option<String>,
661 pub as_number: Option<u32>,
662
663 #[serde(default, skip_serializing_if = "String::is_empty")]
664 pub locale: String,
665 #[serde(default, skip_serializing_if = "String::is_empty")]
666 pub accept_language: String,
667 #[serde(default, skip_serializing_if = "String::is_empty")]
668 pub timezone: String,
669 pub user_agent: String,
670
671 #[serde(default, skip_serializing_if = "String::is_empty")]
674 pub user_agent_version_list: String,
675
676 #[serde(default, skip_serializing_if = "String::is_empty")]
679 pub user_agent_mobile: String,
680
681 #[serde(default, skip_serializing_if = "String::is_empty")]
684 pub os_name: String,
685
686 #[serde(default, skip_serializing_if = "String::is_empty")]
689 pub user_agent_architecture: String,
690
691 #[serde(default, skip_serializing_if = "String::is_empty")]
694 pub user_agent_bitness: String,
695
696 #[serde(default, skip_serializing_if = "String::is_empty")]
699 pub user_agent_full_version_list: String,
700
701 #[serde(default, skip_serializing_if = "String::is_empty")]
704 pub user_agent_model: String,
705
706 #[serde(default, skip_serializing_if = "String::is_empty")]
709 pub os_version: String,
710
711 #[serde(default)]
712 pub screen_width: i32,
713
714 #[serde(default)]
715 pub screen_height: i32,
716
717 #[serde(default)]
718 pub screen_density: f32,
719
720 #[serde(default, skip_serializing_if = "String::is_empty")]
721 pub continent: String,
722
723 #[serde(default, skip_serializing_if = "String::is_empty")]
724 pub country_code: String,
725
726 #[serde(default, skip_serializing_if = "String::is_empty")]
727 pub country_name: String,
728
729 #[serde(default, skip_serializing_if = "String::is_empty")]
730 pub region: String,
731
732 #[serde(default, skip_serializing_if = "String::is_empty")]
733 pub city: String,
734}
735
736#[derive(Serialize, Deserialize, Debug, Clone, Default)]
737pub struct Session {
738 pub session_id: String,
739 #[serde(default)]
740 pub previous_session_id: String,
741 pub session_count: u32,
742 pub session_start: bool,
743 pub first_seen: DateTime<Utc>,
744 pub last_seen: DateTime<Utc>,
745}