Skip to main content

launchdarkly_server_sdk/events/
event.rs

1use std::cmp::{max, min};
2use std::collections::{HashMap, HashSet};
3use std::fmt::{self, Display, Formatter};
4use std::time::Duration;
5
6use launchdarkly_server_sdk_evaluation::{
7    Context, ContextAttributes, Detail, Flag, FlagValue, Kind, Reason, Reference, VariationIndex,
8};
9use serde::ser::SerializeStruct;
10use serde::{Serialize, Serializer};
11
12use crate::migrations::{Operation, Origin, Stage};
13
14#[derive(Clone, Debug, PartialEq)]
15pub struct BaseEvent {
16    pub creation_date: u64,
17    pub context: Context,
18
19    // These attributes will not be serialized. They exist only to help serialize base event into
20    // the right structure
21    inline: bool,
22    all_attribute_private: bool,
23    redact_anonymous: bool,
24    global_private_attributes: HashSet<Reference>,
25}
26
27impl Serialize for BaseEvent {
28    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
29    where
30        S: Serializer,
31    {
32        let mut state = serializer.serialize_struct("BaseEvent", 2)?;
33        state.serialize_field("creationDate", &self.creation_date)?;
34
35        if self.inline {
36            state.serialize_field("context", &self.redacted_context_attributes())?;
37        } else {
38            state.serialize_field("contextKeys", &self.context.context_keys())?;
39        }
40
41        state.end()
42    }
43}
44
45impl BaseEvent {
46    /// Builds a [ContextAttributes] view of this event's context with the configured private
47    /// attribute redaction applied. This is the representation that must be used whenever the
48    /// full context is serialized into an inline event, so that attributes marked private are
49    /// removed before the event leaves the SDK.
50    pub(crate) fn redacted_context_attributes(&self) -> ContextAttributes {
51        if self.redact_anonymous {
52            ContextAttributes::from_context_with_anonymous_redaction(
53                self.context.clone(),
54                self.all_attribute_private,
55                self.global_private_attributes.clone(),
56            )
57        } else {
58            ContextAttributes::from_context(
59                self.context.clone(),
60                self.all_attribute_private,
61                self.global_private_attributes.clone(),
62            )
63        }
64    }
65
66    pub fn new(creation_date: u64, context: Context) -> Self {
67        Self {
68            creation_date,
69            context,
70            inline: false,
71            all_attribute_private: false,
72            global_private_attributes: HashSet::new(),
73            redact_anonymous: false,
74        }
75    }
76
77    pub(crate) fn into_inline(
78        self,
79        all_attribute_private: bool,
80        global_private_attributes: HashSet<Reference>,
81    ) -> Self {
82        Self {
83            inline: true,
84            all_attribute_private,
85            global_private_attributes,
86            ..self
87        }
88    }
89
90    pub(crate) fn into_inline_with_anonymous_redaction(
91        self,
92        all_attribute_private: bool,
93        global_private_attributes: HashSet<Reference>,
94    ) -> Self {
95        Self {
96            inline: true,
97            all_attribute_private,
98            global_private_attributes,
99            redact_anonymous: true,
100            ..self
101        }
102    }
103}
104
105/// A MigrationOpEvent is generated through the migration op tracker provided through the SDK.
106#[derive(Clone, Debug)]
107pub struct MigrationOpEvent {
108    pub(crate) base: BaseEvent,
109    pub(crate) key: String,
110    pub(crate) version: Option<u64>,
111    pub(crate) operation: Operation,
112    pub(crate) default_stage: Stage,
113    pub(crate) evaluation: Detail<Stage>,
114    pub(crate) sampling_ratio: Option<u32>,
115    pub(crate) invoked: HashSet<Origin>,
116    pub(crate) consistency_check: Option<bool>,
117    pub(crate) consistency_check_ratio: Option<u32>,
118    pub(crate) errors: HashSet<Origin>,
119    pub(crate) latency: HashMap<Origin, Duration>,
120}
121
122impl MigrationOpEvent {
123    pub(crate) fn into_inline_with_anonymous_redaction(
124        self,
125        all_attribute_private: bool,
126        global_private_attributes: HashSet<Reference>,
127    ) -> Self {
128        Self {
129            base: self.base.into_inline_with_anonymous_redaction(
130                all_attribute_private,
131                global_private_attributes,
132            ),
133            ..self
134        }
135    }
136}
137
138impl Serialize for MigrationOpEvent {
139    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
140    where
141        S: Serializer,
142    {
143        let mut state = serializer.serialize_struct("MigrationOpEvent", 10)?;
144        state.serialize_field("kind", "migration_op")?;
145        state.serialize_field("creationDate", &self.base.creation_date)?;
146        state.serialize_field("context", &self.base.redacted_context_attributes())?;
147        state.serialize_field("operation", &self.operation)?;
148
149        if !is_default_ratio(&self.sampling_ratio) {
150            state.serialize_field("samplingRatio", &self.sampling_ratio.unwrap_or(1))?;
151        }
152
153        let evaluation = MigrationOpEvaluation {
154            key: self.key.clone(),
155            value: self.evaluation.value,
156            default: self.default_stage,
157            reason: self.evaluation.reason.clone(),
158            variation_index: self.evaluation.variation_index,
159            version: self.version,
160        };
161        state.serialize_field("evaluation", &evaluation)?;
162
163        let mut measurements = vec![];
164        if !self.invoked.is_empty() {
165            measurements.push(MigrationOpMeasurement::Invoked(&self.invoked));
166        }
167
168        if let Some(consistency_check) = self.consistency_check {
169            measurements.push(MigrationOpMeasurement::ConsistencyCheck(
170                consistency_check,
171                self.consistency_check_ratio,
172            ));
173        }
174
175        if !self.errors.is_empty() {
176            measurements.push(MigrationOpMeasurement::Errors(&self.errors));
177        }
178
179        if !self.latency.is_empty() {
180            measurements.push(MigrationOpMeasurement::Latency(&self.latency));
181        }
182
183        if !measurements.is_empty() {
184            state.serialize_field("measurements", &measurements)?;
185        }
186
187        state.end()
188    }
189}
190
191#[derive(Serialize)]
192#[serde(rename_all = "camelCase")]
193struct MigrationOpEvaluation {
194    pub key: String,
195
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub value: Option<Stage>,
198
199    pub(crate) default: Stage,
200
201    pub reason: Reason,
202
203    #[serde(rename = "variation", skip_serializing_if = "Option::is_none")]
204    pub variation_index: Option<VariationIndex>,
205
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub version: Option<u64>,
208}
209
210enum MigrationOpMeasurement<'a> {
211    Invoked(&'a HashSet<Origin>),
212    ConsistencyCheck(bool, Option<u32>),
213    Errors(&'a HashSet<Origin>),
214    Latency(&'a HashMap<Origin, Duration>),
215}
216
217impl Serialize for MigrationOpMeasurement<'_> {
218    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
219    where
220        S: Serializer,
221    {
222        match self {
223            MigrationOpMeasurement::Invoked(invoked) => {
224                let mut state = serializer.serialize_struct("invoked", 2)?;
225                state.serialize_field("key", "invoked")?;
226
227                let invoked = invoked
228                    .iter()
229                    .map(|origin| (origin, true))
230                    .collect::<HashMap<_, _>>();
231                state.serialize_field("values", &invoked)?;
232                state.end()
233            }
234            MigrationOpMeasurement::ConsistencyCheck(consistency_check, consistency_ratio) => {
235                let mut state = serializer.serialize_struct("consistency", 2)?;
236                state.serialize_field("key", "consistent")?;
237                state.serialize_field("value", &consistency_check)?;
238
239                match consistency_ratio {
240                    None | Some(1) => (),
241                    Some(ratio) => state.serialize_field("samplingRatio", &ratio)?,
242                }
243
244                state.end()
245            }
246            MigrationOpMeasurement::Errors(errors) => {
247                let mut state = serializer.serialize_struct("errors", 2)?;
248                state.serialize_field("key", "error")?;
249
250                let errors = errors
251                    .iter()
252                    .map(|origin| (origin, true))
253                    .collect::<HashMap<_, _>>();
254                state.serialize_field("values", &errors)?;
255                state.end()
256            }
257            MigrationOpMeasurement::Latency(latency) => {
258                let mut state = serializer.serialize_struct("latencies", 2)?;
259                state.serialize_field("key", "latency_ms")?;
260                let latencies = latency
261                    .iter()
262                    .map(|(origin, duration)| (origin, duration.as_millis() as u64))
263                    .collect::<HashMap<_, _>>();
264                state.serialize_field("values", &latencies)?;
265                state.end()
266            }
267        }
268    }
269}
270
271#[derive(Clone, Debug, PartialEq, Serialize)]
272#[serde(rename_all = "camelCase")]
273pub struct FeatureRequestEvent {
274    #[serde(flatten)]
275    pub(crate) base: BaseEvent,
276    key: String,
277    value: FlagValue,
278    variation: Option<VariationIndex>,
279    default: FlagValue,
280    #[serde(skip_serializing_if = "Option::is_none")]
281    reason: Option<Reason>,
282    version: Option<u64>,
283    #[serde(skip_serializing_if = "Option::is_none")]
284    prereq_of: Option<String>,
285
286    #[serde(skip)]
287    pub(crate) track_events: bool,
288
289    #[serde(skip)]
290    pub(crate) debug_events_until_date: Option<u64>,
291
292    #[serde(skip_serializing_if = "is_default_ratio")]
293    pub(crate) sampling_ratio: Option<u32>,
294
295    #[serde(skip_serializing_if = "std::ops::Not::not")]
296    pub(crate) exclude_from_summaries: bool,
297}
298
299impl FeatureRequestEvent {
300    pub fn to_index_event(
301        &self,
302        all_attribute_private: bool,
303        global_private_attributes: HashSet<Reference>,
304    ) -> IndexEvent {
305        self.base
306            .clone()
307            .into_inline(all_attribute_private, global_private_attributes)
308            .into()
309    }
310
311    pub(crate) fn into_inline(
312        self,
313        all_attribute_private: bool,
314        global_private_attributes: HashSet<Reference>,
315    ) -> Self {
316        Self {
317            base: self
318                .base
319                .into_inline(all_attribute_private, global_private_attributes),
320            ..self
321        }
322    }
323
324    pub(crate) fn into_inline_with_anonymous_redaction(
325        self,
326        all_attribute_private: bool,
327        global_private_attributes: HashSet<Reference>,
328    ) -> Self {
329        Self {
330            base: self.base.into_inline_with_anonymous_redaction(
331                all_attribute_private,
332                global_private_attributes,
333            ),
334            ..self
335        }
336    }
337}
338
339#[derive(Clone, Debug, PartialEq, Serialize)]
340pub struct IndexEvent {
341    #[serde(flatten)]
342    base: BaseEvent,
343}
344
345impl From<BaseEvent> for IndexEvent {
346    fn from(base: BaseEvent) -> Self {
347        let base = BaseEvent {
348            inline: true,
349            ..base
350        };
351
352        Self { base }
353    }
354}
355
356#[derive(Clone, Debug, PartialEq, Serialize)]
357pub struct IdentifyEvent {
358    #[serde(flatten)]
359    pub(crate) base: BaseEvent,
360    key: String,
361    #[serde(skip_serializing_if = "is_default_ratio")]
362    pub(crate) sampling_ratio: Option<u32>,
363}
364
365impl IdentifyEvent {
366    pub(crate) fn into_inline(
367        self,
368        all_attribute_private: bool,
369        global_private_attributes: HashSet<Reference>,
370    ) -> Self {
371        Self {
372            base: self
373                .base
374                .into_inline(all_attribute_private, global_private_attributes),
375            ..self
376        }
377    }
378}
379
380#[derive(Clone, Debug, PartialEq, Serialize)]
381#[serde(rename_all = "camelCase")]
382pub struct CustomEvent {
383    #[serde(flatten)]
384    pub(crate) base: BaseEvent,
385    key: String,
386    #[serde(skip_serializing_if = "Option::is_none")]
387    metric_value: Option<f64>,
388    #[serde(skip_serializing_if = "serde_json::Value::is_null")]
389    data: serde_json::Value,
390    #[serde(skip_serializing_if = "is_default_ratio")]
391    pub(crate) sampling_ratio: Option<u32>,
392}
393
394impl CustomEvent {
395    pub(crate) fn into_inline_with_anonymous_redaction(
396        self,
397        all_attribute_private: bool,
398        global_private_attributes: HashSet<Reference>,
399    ) -> Self {
400        Self {
401            base: self.base.into_inline_with_anonymous_redaction(
402                all_attribute_private,
403                global_private_attributes,
404            ),
405            ..self
406        }
407    }
408
409    pub fn to_index_event(
410        &self,
411        all_attribute_private: bool,
412        global_private_attributes: HashSet<Reference>,
413    ) -> IndexEvent {
414        self.base
415            .clone()
416            .into_inline(all_attribute_private, global_private_attributes)
417            .into()
418    }
419}
420
421#[derive(Clone, Debug, Serialize)]
422#[serde(tag = "kind")]
423#[allow(clippy::large_enum_variant)]
424pub enum OutputEvent {
425    #[serde(rename = "index")]
426    Index(IndexEvent),
427
428    #[serde(rename = "debug")]
429    Debug(FeatureRequestEvent),
430
431    #[serde(rename = "feature")]
432    FeatureRequest(FeatureRequestEvent),
433
434    #[serde(rename = "identify")]
435    Identify(IdentifyEvent),
436
437    #[serde(rename = "custom")]
438    Custom(CustomEvent),
439
440    #[serde(rename = "summary")]
441    Summary(EventSummary),
442
443    #[serde(rename = "migration_op")]
444    MigrationOp(MigrationOpEvent),
445}
446
447impl OutputEvent {
448    #[cfg(test)]
449    pub fn kind(&self) -> &'static str {
450        match self {
451            OutputEvent::Index { .. } => "index",
452            OutputEvent::Debug { .. } => "debug",
453            OutputEvent::FeatureRequest { .. } => "feature",
454            OutputEvent::Identify { .. } => "identify",
455            OutputEvent::Custom { .. } => "custom",
456            OutputEvent::Summary { .. } => "summary",
457            OutputEvent::MigrationOp { .. } => "migration_op",
458        }
459    }
460}
461
462#[allow(clippy::large_enum_variant)]
463#[derive(Clone, Debug, Serialize)]
464pub enum InputEvent {
465    FeatureRequest(FeatureRequestEvent),
466    Identify(IdentifyEvent),
467    Custom(CustomEvent),
468    MigrationOp(MigrationOpEvent),
469}
470
471impl InputEvent {
472    #[cfg(test)]
473    pub fn base_mut(&mut self) -> Option<&mut BaseEvent> {
474        match self {
475            InputEvent::FeatureRequest(FeatureRequestEvent { base, .. }) => Some(base),
476            InputEvent::Identify(IdentifyEvent { base, .. }) => Some(base),
477            InputEvent::Custom(CustomEvent { base, .. }) => Some(base),
478            InputEvent::MigrationOp(MigrationOpEvent { base, .. }) => Some(base),
479        }
480    }
481}
482
483impl Display for InputEvent {
484    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
485        let json = serde_json::to_string_pretty(self)
486            .unwrap_or_else(|e| format!("JSON serialization failed ({e}): {self:?}"));
487        write!(f, "{json}")
488    }
489}
490
491pub struct EventFactory {
492    send_reason: bool,
493}
494
495impl EventFactory {
496    pub fn new(send_reason: bool) -> Self {
497        Self { send_reason }
498    }
499
500    pub(crate) fn now() -> u64 {
501        std::time::SystemTime::now()
502            .duration_since(std::time::UNIX_EPOCH)
503            .unwrap()
504            .as_millis() as u64
505    }
506
507    pub fn new_unknown_flag_event(
508        &self,
509        flag_key: &str,
510        context: Context,
511        detail: Detail<FlagValue>,
512        default: FlagValue,
513    ) -> InputEvent {
514        self.new_feature_request_event(flag_key, context, None, detail, default, None)
515    }
516
517    pub fn new_eval_event(
518        &self,
519        flag_key: &str,
520        context: Context,
521        flag: &Flag,
522        detail: Detail<FlagValue>,
523        default: FlagValue,
524        prereq_of: Option<String>,
525    ) -> InputEvent {
526        self.new_feature_request_event(flag_key, context, Some(flag), detail, default, prereq_of)
527    }
528
529    fn new_feature_request_event(
530        &self,
531        flag_key: &str,
532        context: Context,
533        flag: Option<&Flag>,
534        detail: Detail<FlagValue>,
535        default: FlagValue,
536        prereq_of: Option<String>,
537    ) -> InputEvent {
538        let value = detail
539            .value
540            .unwrap_or(FlagValue::Json(serde_json::Value::Null));
541
542        let flag_track_events;
543        let require_experiment_data;
544        let debug_events_until_date;
545        let sampling_ratio;
546        let exclude_from_summaries;
547
548        if let Some(f) = flag {
549            flag_track_events = f.track_events;
550            require_experiment_data = f.is_experimentation_enabled(&detail.reason);
551            debug_events_until_date = f.debug_events_until_date;
552            sampling_ratio = f.sampling_ratio;
553            exclude_from_summaries = f.exclude_from_summaries;
554        } else {
555            flag_track_events = false;
556            require_experiment_data = false;
557            debug_events_until_date = None;
558            sampling_ratio = None;
559            exclude_from_summaries = false;
560        }
561
562        let reason = if self.send_reason || require_experiment_data {
563            Some(detail.reason)
564        } else {
565            None
566        };
567
568        InputEvent::FeatureRequest(FeatureRequestEvent {
569            base: BaseEvent::new(Self::now(), context),
570            key: flag_key.to_owned(),
571            default,
572            reason,
573            value,
574            variation: detail.variation_index,
575            version: flag.map(|f| f.version),
576            prereq_of,
577            track_events: flag_track_events || require_experiment_data,
578            debug_events_until_date,
579            sampling_ratio,
580            exclude_from_summaries,
581        })
582    }
583
584    pub fn new_identify(&self, context: Context) -> InputEvent {
585        InputEvent::Identify(IdentifyEvent {
586            key: context.key().to_owned(),
587            base: BaseEvent::new(Self::now(), context),
588            sampling_ratio: None,
589        })
590    }
591
592    pub(crate) fn new_migration_op(&self, event: MigrationOpEvent) -> InputEvent {
593        InputEvent::MigrationOp(event)
594    }
595
596    pub fn new_custom(
597        &self,
598        context: Context,
599        key: impl Into<String>,
600        metric_value: Option<f64>,
601        data: impl Serialize,
602    ) -> serde_json::Result<InputEvent> {
603        let data = serde_json::to_value(data)?;
604
605        Ok(InputEvent::Custom(CustomEvent {
606            base: BaseEvent::new(Self::now(), context),
607            key: key.into(),
608            metric_value,
609            data,
610            sampling_ratio: None,
611        }))
612    }
613}
614
615#[derive(Clone, Debug, Serialize)]
616#[serde(into = "EventSummaryOutput")]
617pub struct EventSummary {
618    pub(crate) start_date: u64,
619    pub(crate) end_date: u64,
620    pub(crate) features: HashMap<String, FlagSummary>,
621}
622
623impl Default for EventSummary {
624    fn default() -> Self {
625        EventSummary::new()
626    }
627}
628
629impl EventSummary {
630    pub fn new() -> Self {
631        EventSummary {
632            start_date: u64::MAX,
633            end_date: 0,
634            features: HashMap::new(),
635        }
636    }
637
638    pub fn is_empty(&self) -> bool {
639        self.features.is_empty()
640    }
641
642    pub fn add(&mut self, event: &FeatureRequestEvent) {
643        let FeatureRequestEvent {
644            base:
645                BaseEvent {
646                    creation_date,
647                    context,
648                    ..
649                },
650            key,
651            value,
652            version,
653            variation,
654            default,
655            ..
656        } = event;
657
658        self.start_date = min(self.start_date, *creation_date);
659        self.end_date = max(self.end_date, *creation_date);
660
661        let variation_key = VariationKey {
662            version: *version,
663            variation: *variation,
664        };
665
666        let feature = self
667            .features
668            .entry(key.clone())
669            .or_insert_with(|| FlagSummary::new(default.clone()));
670
671        feature.track(variation_key, value, context);
672    }
673
674    pub fn reset(&mut self) {
675        self.features.clear();
676        self.start_date = u64::MAX;
677        self.end_date = 0;
678    }
679}
680
681#[derive(Clone, Debug)]
682pub struct FlagSummary {
683    pub(crate) counters: HashMap<VariationKey, VariationSummary>,
684    pub(crate) default: FlagValue,
685    pub(crate) context_kinds: HashSet<Kind>,
686}
687
688impl FlagSummary {
689    pub fn new(default: FlagValue) -> Self {
690        Self {
691            counters: HashMap::new(),
692            default,
693            context_kinds: HashSet::new(),
694        }
695    }
696
697    pub fn track(
698        &mut self,
699        variation_key: VariationKey,
700        value: &FlagValue,
701        context: &Context,
702    ) -> &mut Self {
703        if let Some(summary) = self.counters.get_mut(&variation_key) {
704            summary.count_request();
705        } else {
706            self.counters
707                .insert(variation_key, VariationSummary::new(value.clone()));
708        }
709
710        for kind in context.kinds() {
711            self.context_kinds.insert(kind.clone());
712        }
713
714        self
715    }
716}
717
718#[derive(Clone, Debug, Eq, Hash, PartialEq)]
719pub struct VariationKey {
720    pub version: Option<u64>,
721    pub variation: Option<VariationIndex>,
722}
723
724#[derive(Clone, Debug, PartialEq)]
725pub struct VariationSummary {
726    pub count: u64,
727    pub value: FlagValue,
728}
729
730impl VariationSummary {
731    fn new(value: FlagValue) -> Self {
732        VariationSummary { count: 1, value }
733    }
734
735    fn count_request(&mut self) {
736        self.count += 1;
737    }
738}
739
740// Implement event summarisation a second time because we report it summarised a different way than
741// we collected it.
742//
743// (See #[serde(into)] annotation on EventSummary.)
744
745#[derive(Serialize)]
746#[serde(rename_all = "camelCase")]
747struct EventSummaryOutput {
748    start_date: u64,
749    end_date: u64,
750    features: HashMap<String, FeatureSummaryOutput>,
751}
752
753impl From<EventSummary> for EventSummaryOutput {
754    fn from(summary: EventSummary) -> Self {
755        let features = summary
756            .features
757            .into_iter()
758            .map(|(key, value)| (key, value.into()))
759            .collect();
760
761        EventSummaryOutput {
762            start_date: summary.start_date,
763            end_date: summary.end_date,
764            features,
765        }
766    }
767}
768
769#[derive(Serialize)]
770#[serde(rename_all = "camelCase")]
771struct FeatureSummaryOutput {
772    default: FlagValue,
773    context_kinds: HashSet<Kind>,
774    counters: Vec<VariationCounterOutput>,
775}
776
777impl From<FlagSummary> for FeatureSummaryOutput {
778    fn from(flag_summary: FlagSummary) -> Self {
779        let counters = flag_summary
780            .counters
781            .into_iter()
782            .map(|(variation_key, variation_summary)| (variation_key, variation_summary).into())
783            .collect::<Vec<VariationCounterOutput>>();
784
785        Self {
786            default: flag_summary.default,
787            context_kinds: flag_summary.context_kinds,
788            counters,
789        }
790    }
791}
792
793#[derive(Serialize)]
794struct VariationCounterOutput {
795    pub value: FlagValue,
796    #[serde(skip_serializing_if = "Option::is_none")]
797    pub unknown: Option<bool>,
798    #[serde(skip_serializing_if = "Option::is_none")]
799    pub version: Option<u64>,
800    pub count: u64,
801    #[serde(skip_serializing_if = "Option::is_none")]
802    pub variation: Option<VariationIndex>,
803}
804
805impl From<(VariationKey, VariationSummary)> for VariationCounterOutput {
806    fn from((variation_key, variation_summary): (VariationKey, VariationSummary)) -> Self {
807        VariationCounterOutput {
808            value: variation_summary.value,
809            unknown: variation_key.version.map_or(Some(true), |_| None),
810            version: variation_key.version,
811            count: variation_summary.count,
812            variation: variation_key.variation,
813        }
814    }
815}
816
817// Used strictly for serialization to determine if a ratio should be included in the JSON.
818fn is_default_ratio(sampling_ratio: &Option<u32>) -> bool {
819    sampling_ratio.unwrap_or(1) == 1
820}
821
822#[cfg(test)]
823mod tests {
824    use launchdarkly_server_sdk_evaluation::{
825        AttributeValue, ContextBuilder, Kind, MultiContextBuilder,
826    };
827    use maplit::{hashmap, hashset};
828
829    use super::*;
830    use crate::test_common::basic_flag;
831    use assert_json_diff::assert_json_eq;
832    use serde_json::json;
833    use test_case::test_case;
834
835    #[test]
836    fn serializes_feature_request_event() {
837        let flag = basic_flag("flag");
838        let default = FlagValue::from(false);
839        let context = ContextBuilder::new("alice")
840            .anonymous(true)
841            .build()
842            .expect("Failed to create context");
843        let fallthrough = Detail {
844            value: Some(FlagValue::from(false)),
845            variation_index: Some(1),
846            reason: Reason::Fallthrough {
847                in_experiment: false,
848            },
849        };
850
851        let event_factory = EventFactory::new(true);
852        let mut feature_request_event =
853            event_factory.new_eval_event(&flag.key, context, &flag, fallthrough, default, None);
854        // fix creation date so JSON is predictable
855        feature_request_event.base_mut().unwrap().creation_date = 1234;
856
857        if let InputEvent::FeatureRequest(feature_request_event) = feature_request_event {
858            let output_event = OutputEvent::FeatureRequest(
859                feature_request_event.into_inline(false, HashSet::new()),
860            );
861            let event_json = json!({
862              "kind": "feature",
863              "creationDate": 1234,
864              "context": {
865                "key": "alice",
866                "kind": "user",
867                "anonymous": true
868              },
869              "key": "flag",
870              "value": false,
871              "variation": 1,
872              "default": false,
873              "reason": {
874                "kind": "FALLTHROUGH"
875              },
876              "version": 42
877            });
878
879            assert_json_eq!(output_event, event_json);
880        }
881    }
882
883    #[test]
884    fn serializes_feature_request_event_with_global_private_attribute() {
885        let flag = basic_flag("flag");
886        let default = FlagValue::from(false);
887        let context = ContextBuilder::new("alice")
888            .anonymous(true)
889            .set_value("foo", AttributeValue::Bool(true))
890            .build()
891            .expect("Failed to create context");
892        let fallthrough = Detail {
893            value: Some(FlagValue::from(false)),
894            variation_index: Some(1),
895            reason: Reason::Fallthrough {
896                in_experiment: false,
897            },
898        };
899
900        let event_factory = EventFactory::new(true);
901        let mut feature_request_event =
902            event_factory.new_eval_event(&flag.key, context, &flag, fallthrough, default, None);
903        // fix creation date so JSON is predictable
904        feature_request_event.base_mut().unwrap().creation_date = 1234;
905
906        if let InputEvent::FeatureRequest(feature_request_event) = feature_request_event {
907            let output_event = OutputEvent::FeatureRequest(
908                feature_request_event.into_inline(false, hashset!["foo".into()]),
909            );
910            let event_json = json!({
911              "kind": "feature",
912              "creationDate": 1234,
913              "context": {
914                "key": "alice",
915                "kind": "user",
916                "anonymous": true,
917                "_meta" : {
918                    "redactedAttributes" : ["foo"]
919                }
920              },
921              "key": "flag",
922              "value": false,
923              "variation": 1,
924              "default": false,
925              "reason": {
926                "kind": "FALLTHROUGH"
927              },
928              "version": 42
929            });
930
931            assert_json_eq!(output_event, event_json);
932        }
933    }
934
935    #[test]
936    fn serializes_feature_request_event_with_all_private_attributes() {
937        let flag = basic_flag("flag");
938        let default = FlagValue::from(false);
939        let context = ContextBuilder::new("alice")
940            .anonymous(true)
941            .set_value("foo", AttributeValue::Bool(true))
942            .build()
943            .expect("Failed to create context");
944        let fallthrough = Detail {
945            value: Some(FlagValue::from(false)),
946            variation_index: Some(1),
947            reason: Reason::Fallthrough {
948                in_experiment: false,
949            },
950        };
951
952        let event_factory = EventFactory::new(true);
953        let mut feature_request_event =
954            event_factory.new_eval_event(&flag.key, context, &flag, fallthrough, default, None);
955        // fix creation date so JSON is predictable
956        feature_request_event.base_mut().unwrap().creation_date = 1234;
957
958        if let InputEvent::FeatureRequest(feature_request_event) = feature_request_event {
959            let output_event = OutputEvent::FeatureRequest(
960                feature_request_event.into_inline(true, HashSet::new()),
961            );
962            let event_json = json!({
963              "kind": "feature",
964              "creationDate": 1234,
965              "context": {
966                "_meta": {
967                  "redactedAttributes" : ["foo"]
968                },
969                "key": "alice",
970                "kind": "user",
971                "anonymous": true
972              },
973              "key": "flag",
974              "value": false,
975              "variation": 1,
976              "default": false,
977              "reason": {
978                "kind": "FALLTHROUGH"
979              },
980              "version": 42
981            });
982
983            assert_json_eq!(output_event, event_json);
984        }
985    }
986
987    #[test]
988    fn serializes_feature_request_event_with_anonymous_attribute_redaction() {
989        let flag = basic_flag("flag");
990        let default = FlagValue::from(false);
991        let context = ContextBuilder::new("alice")
992            .anonymous(true)
993            .set_value("foo", AttributeValue::Bool(true))
994            .build()
995            .expect("Failed to create context");
996        let fallthrough = Detail {
997            value: Some(FlagValue::from(false)),
998            variation_index: Some(1),
999            reason: Reason::Fallthrough {
1000                in_experiment: false,
1001            },
1002        };
1003
1004        let event_factory = EventFactory::new(true);
1005        let mut feature_request_event =
1006            event_factory.new_eval_event(&flag.key, context, &flag, fallthrough, default, None);
1007        // fix creation date so JSON is predictable
1008        feature_request_event.base_mut().unwrap().creation_date = 1234;
1009
1010        if let InputEvent::FeatureRequest(feature_request_event) = feature_request_event {
1011            let output_event = OutputEvent::FeatureRequest(
1012                feature_request_event.into_inline_with_anonymous_redaction(false, HashSet::new()),
1013            );
1014            let event_json = json!({
1015              "kind": "feature",
1016              "creationDate": 1234,
1017              "context": {
1018                "_meta": {
1019                  "redactedAttributes" : ["foo"]
1020                },
1021                "key": "alice",
1022                "kind": "user",
1023                "anonymous": true
1024              },
1025              "key": "flag",
1026              "value": false,
1027              "variation": 1,
1028              "default": false,
1029              "reason": {
1030                "kind": "FALLTHROUGH"
1031              },
1032              "version": 42
1033            });
1034
1035            assert_json_eq!(output_event, event_json);
1036        }
1037    }
1038
1039    #[test]
1040    fn serializes_feature_request_event_with_anonymous_attribute_redaction_in_multikind_context() {
1041        let flag = basic_flag("flag");
1042        let default = FlagValue::from(false);
1043        let user_context = ContextBuilder::new("alice")
1044            .anonymous(true)
1045            .set_value("foo", AttributeValue::Bool(true))
1046            .build()
1047            .expect("Failed to create user context");
1048        let org_context = ContextBuilder::new("LaunchDarkly")
1049            .kind("org")
1050            .set_value("foo", AttributeValue::Bool(true))
1051            .build()
1052            .expect("Failed to create org context");
1053        let multi_context = MultiContextBuilder::new()
1054            .add_context(user_context)
1055            .add_context(org_context)
1056            .build()
1057            .expect("Failed to create multi context");
1058        let fallthrough = Detail {
1059            value: Some(FlagValue::from(false)),
1060            variation_index: Some(1),
1061            reason: Reason::Fallthrough {
1062                in_experiment: false,
1063            },
1064        };
1065
1066        let event_factory = EventFactory::new(true);
1067        let mut feature_request_event = event_factory.new_eval_event(
1068            &flag.key,
1069            multi_context,
1070            &flag,
1071            fallthrough,
1072            default,
1073            None,
1074        );
1075        // fix creation date so JSON is predictable
1076        feature_request_event.base_mut().unwrap().creation_date = 1234;
1077
1078        if let InputEvent::FeatureRequest(feature_request_event) = feature_request_event {
1079            let output_event = OutputEvent::FeatureRequest(
1080                feature_request_event.into_inline_with_anonymous_redaction(false, HashSet::new()),
1081            );
1082            let event_json = json!({
1083              "kind": "feature",
1084              "creationDate": 1234,
1085              "context": {
1086                "kind": "multi",
1087                "user": {
1088                    "_meta": {
1089                    "redactedAttributes" : ["foo"]
1090                    },
1091                    "key": "alice",
1092                    "anonymous": true
1093                },
1094                "org": {
1095                    "foo": true,
1096                    "key": "LaunchDarkly"
1097                }
1098              },
1099              "key": "flag",
1100              "value": false,
1101              "variation": 1,
1102              "default": false,
1103              "reason": {
1104                "kind": "FALLTHROUGH"
1105              },
1106              "version": 42
1107            });
1108
1109            assert_json_eq!(output_event, event_json);
1110        }
1111    }
1112
1113    #[test]
1114    fn serializes_feature_request_event_with_local_private_attribute() {
1115        let flag = basic_flag("flag");
1116        let default = FlagValue::from(false);
1117        let context = ContextBuilder::new("alice")
1118            .anonymous(true)
1119            .set_value("foo", AttributeValue::Bool(true))
1120            .add_private_attribute("foo")
1121            .build()
1122            .expect("Failed to create context");
1123        let fallthrough = Detail {
1124            value: Some(FlagValue::from(false)),
1125            variation_index: Some(1),
1126            reason: Reason::Fallthrough {
1127                in_experiment: false,
1128            },
1129        };
1130
1131        let event_factory = EventFactory::new(true);
1132        let mut feature_request_event =
1133            event_factory.new_eval_event(&flag.key, context, &flag, fallthrough, default, None);
1134        // fix creation date so JSON is predictable
1135        feature_request_event.base_mut().unwrap().creation_date = 1234;
1136
1137        if let InputEvent::FeatureRequest(feature_request_event) = feature_request_event {
1138            let output_event = OutputEvent::FeatureRequest(
1139                feature_request_event.into_inline(false, HashSet::new()),
1140            );
1141            let event_json = json!({
1142              "kind": "feature",
1143              "creationDate": 1234,
1144              "context": {
1145                "_meta": {
1146                  "redactedAttributes" : ["foo"]
1147                },
1148                "key": "alice",
1149                "kind": "user",
1150                "anonymous": true
1151              },
1152              "key": "flag",
1153              "value": false,
1154              "variation": 1,
1155              "default": false,
1156              "reason": {
1157                "kind": "FALLTHROUGH"
1158              },
1159              "version": 42
1160            });
1161
1162            assert_json_eq!(output_event, event_json);
1163        }
1164    }
1165
1166    // Builds a minimal migration op event for the given context. Migration op events always inline
1167    // the full context, so these tests exercise the redaction path the dispatcher relies on.
1168    fn migration_op_event(context: Context) -> MigrationOpEvent {
1169        MigrationOpEvent {
1170            base: BaseEvent::new(1234, context),
1171            key: "migration-key".into(),
1172            version: None,
1173            operation: Operation::Read,
1174            default_stage: Stage::Live,
1175            evaluation: Detail {
1176                value: Some(Stage::Live),
1177                variation_index: Some(1),
1178                reason: Reason::Fallthrough {
1179                    in_experiment: false,
1180                },
1181            },
1182            sampling_ratio: None,
1183            invoked: hashset![Origin::Old],
1184            consistency_check: None,
1185            consistency_check_ratio: None,
1186            errors: HashSet::new(),
1187            latency: HashMap::new(),
1188        }
1189    }
1190
1191    #[test]
1192    fn migration_op_event_redacts_global_private_attribute() {
1193        let context = ContextBuilder::new("alice")
1194            .set_value("foo", AttributeValue::Bool(true))
1195            .build()
1196            .expect("Failed to create context");
1197        let event = migration_op_event(context)
1198            .into_inline_with_anonymous_redaction(false, hashset!["foo".into()]);
1199        let output = serde_json::to_value(OutputEvent::MigrationOp(event))
1200            .expect("Failed to serialize event");
1201
1202        assert_json_eq!(
1203            output["context"],
1204            json!({
1205                "key": "alice",
1206                "kind": "user",
1207                "_meta": { "redactedAttributes": ["foo"] }
1208            })
1209        );
1210    }
1211
1212    #[test]
1213    fn migration_op_event_redacts_all_private_attributes() {
1214        let context = ContextBuilder::new("alice")
1215            .set_value("foo", AttributeValue::Bool(true))
1216            .build()
1217            .expect("Failed to create context");
1218        let event =
1219            migration_op_event(context).into_inline_with_anonymous_redaction(true, HashSet::new());
1220        let output = serde_json::to_value(OutputEvent::MigrationOp(event))
1221            .expect("Failed to serialize event");
1222
1223        assert_json_eq!(
1224            output["context"],
1225            json!({
1226                "key": "alice",
1227                "kind": "user",
1228                "_meta": { "redactedAttributes": ["foo"] }
1229            })
1230        );
1231    }
1232
1233    #[test]
1234    fn migration_op_event_redacts_context_declared_private_attribute() {
1235        let context = ContextBuilder::new("alice")
1236            .set_value("foo", AttributeValue::Bool(true))
1237            .add_private_attribute("foo")
1238            .build()
1239            .expect("Failed to create context");
1240        let event =
1241            migration_op_event(context).into_inline_with_anonymous_redaction(false, HashSet::new());
1242        let output = serde_json::to_value(OutputEvent::MigrationOp(event))
1243            .expect("Failed to serialize event");
1244
1245        assert_json_eq!(
1246            output["context"],
1247            json!({
1248                "key": "alice",
1249                "kind": "user",
1250                "_meta": { "redactedAttributes": ["foo"] }
1251            })
1252        );
1253    }
1254
1255    #[test]
1256    fn migration_op_event_redacts_anonymous_context_attributes() {
1257        let context = ContextBuilder::new("alice")
1258            .anonymous(true)
1259            .set_value("foo", AttributeValue::Bool(true))
1260            .build()
1261            .expect("Failed to create context");
1262        let event =
1263            migration_op_event(context).into_inline_with_anonymous_redaction(false, HashSet::new());
1264        let output = serde_json::to_value(OutputEvent::MigrationOp(event))
1265            .expect("Failed to serialize event");
1266
1267        assert_json_eq!(
1268            output["context"],
1269            json!({
1270                "key": "alice",
1271                "kind": "user",
1272                "anonymous": true,
1273                "_meta": { "redactedAttributes": ["foo"] }
1274            })
1275        );
1276    }
1277
1278    #[test]
1279    fn serializes_feature_request_event_without_inlining_user() {
1280        let flag = basic_flag("flag");
1281        let default = FlagValue::from(false);
1282        let context = ContextBuilder::new("alice")
1283            .anonymous(true)
1284            .build()
1285            .expect("Failed to create context");
1286        let fallthrough = Detail {
1287            value: Some(FlagValue::from(false)),
1288            variation_index: Some(1),
1289            reason: Reason::Fallthrough {
1290                in_experiment: false,
1291            },
1292        };
1293
1294        let event_factory = EventFactory::new(true);
1295        let mut feature_request_event =
1296            event_factory.new_eval_event(&flag.key, context, &flag, fallthrough, default, None);
1297        // fix creation date so JSON is predictable
1298        feature_request_event.base_mut().unwrap().creation_date = 1234;
1299
1300        if let InputEvent::FeatureRequest(feature_request_event) = feature_request_event {
1301            let output_event = OutputEvent::FeatureRequest(feature_request_event);
1302            let event_json = json!({
1303                "kind": "feature",
1304                "creationDate": 1234,
1305                "contextKeys": {
1306                    "user": "alice"
1307                },
1308                "key": "flag",
1309                "value": false,
1310                "variation": 1,
1311                "default": false,
1312                "reason": {
1313                    "kind": "FALLTHROUGH"
1314                },
1315                "version": 42
1316            });
1317            assert_json_eq!(output_event, event_json);
1318        }
1319    }
1320
1321    #[test]
1322    fn serializes_identify_event() {
1323        let context = ContextBuilder::new("alice")
1324            .anonymous(true)
1325            .build()
1326            .expect("Failed to create context");
1327        let event_factory = EventFactory::new(true);
1328        let mut identify = event_factory.new_identify(context);
1329        identify.base_mut().unwrap().creation_date = 1234;
1330
1331        if let InputEvent::Identify(identify) = identify {
1332            let output_event = OutputEvent::Identify(identify.into_inline(false, HashSet::new()));
1333            let event_json = json!({
1334              "kind": "identify",
1335              "creationDate": 1234,
1336              "context": {
1337                "key": "alice",
1338                "kind": "user",
1339                "anonymous": true
1340              },
1341              "key": "alice"
1342            });
1343            assert_json_eq!(output_event, event_json);
1344        }
1345    }
1346
1347    #[test]
1348    fn serializes_custom_event() {
1349        let context = ContextBuilder::new("alice")
1350            .anonymous(true)
1351            .build()
1352            .expect("Failed to create context");
1353
1354        let event_factory = EventFactory::new(true);
1355        let mut custom_event = event_factory
1356            .new_custom(
1357                context,
1358                "custom-key",
1359                Some(12345.0),
1360                serde_json::Value::Null,
1361            )
1362            .unwrap();
1363        // fix creation date so JSON is predictable
1364        custom_event.base_mut().unwrap().creation_date = 1234;
1365
1366        if let InputEvent::Custom(custom_event) = custom_event {
1367            let output_event = OutputEvent::Custom(custom_event);
1368            let event_json = json!({
1369                "kind": "custom",
1370                "creationDate": 1234,
1371                "contextKeys": {
1372                    "user": "alice"
1373                },
1374                "key": "custom-key",
1375                "metricValue": 12345.0
1376            });
1377            assert_json_eq!(output_event, event_json);
1378        }
1379    }
1380
1381    #[test]
1382    fn serializes_custom_event_without_inlining_user() {
1383        let context = ContextBuilder::new("alice")
1384            .anonymous(true)
1385            .build()
1386            .expect("Failed to create context");
1387
1388        let event_factory = EventFactory::new(true);
1389        let mut custom_event = event_factory
1390            .new_custom(
1391                context,
1392                "custom-key",
1393                Some(12345.0),
1394                serde_json::Value::Null,
1395            )
1396            .unwrap();
1397        // fix creation date so JSON is predictable
1398        custom_event.base_mut().unwrap().creation_date = 1234;
1399
1400        if let InputEvent::Custom(custom_event) = custom_event {
1401            let output_event = OutputEvent::Custom(custom_event);
1402            let event_json = json!({
1403                "kind": "custom",
1404                "creationDate": 1234,
1405                "contextKeys": {
1406                    "user": "alice"
1407                },
1408                "key": "custom-key",
1409                "metricValue": 12345.0
1410            });
1411            assert_json_eq!(output_event, event_json);
1412        }
1413    }
1414
1415    #[test]
1416    fn serializes_summary_event() {
1417        let summary = EventSummary {
1418            start_date: 1234,
1419            end_date: 4567,
1420            features: hashmap! {
1421                "f".into() => FlagSummary {
1422                    counters: hashmap! {
1423                        VariationKey{version: Some(2), variation: Some(1)} => VariationSummary{count: 1, value: true.into()},
1424                    },
1425                    default: false.into(),
1426                    context_kinds: HashSet::new(),
1427                }
1428            },
1429        };
1430        let summary_event = OutputEvent::Summary(summary);
1431
1432        let event_json = json!({
1433            "kind": "summary",
1434            "startDate": 1234,
1435            "endDate": 4567,
1436            "features": {
1437                "f": {
1438                    "default": false,
1439                    "contextKinds": [],
1440                    "counters": [{
1441                        "value": true,
1442                        "version": 2,
1443                        "count": 1,
1444                        "variation": 1
1445                    }]
1446                }
1447        }});
1448        assert_json_eq!(summary_event, event_json);
1449    }
1450
1451    #[test]
1452    fn summary_resets_appropriately() {
1453        let mut summary = EventSummary {
1454            start_date: 1234,
1455            end_date: 4567,
1456            features: hashmap! {
1457                    "f".into() => FlagSummary {
1458                        counters: hashmap!{
1459                            VariationKey{version: Some(2), variation: Some(1)} => VariationSummary{count: 1, value: true.into()}
1460                        },
1461                        default: false.into(),
1462                        context_kinds: HashSet::new(),
1463                }
1464            },
1465        };
1466
1467        summary.reset();
1468
1469        assert!(summary.features.is_empty());
1470        assert_eq!(summary.start_date, u64::MAX);
1471        assert_eq!(summary.end_date, 0);
1472    }
1473
1474    #[test]
1475    fn serializes_index_event() {
1476        let context = ContextBuilder::new("alice")
1477            .anonymous(true)
1478            .build()
1479            .expect("Failed to create context");
1480        let base_event = BaseEvent::new(1234, context);
1481        let index_event = OutputEvent::Index(base_event.into());
1482
1483        let event_json = json!({
1484              "kind": "index",
1485              "creationDate": 1234,
1486              "context": {
1487                "key": "alice",
1488                "kind": "user",
1489                "anonymous": true
1490              }
1491        });
1492
1493        assert_json_eq!(index_event, event_json);
1494    }
1495
1496    #[test]
1497    fn summarises_feature_request() {
1498        let mut summary = EventSummary::new();
1499        assert!(summary.is_empty());
1500        assert!(summary.start_date > summary.end_date);
1501
1502        let flag = basic_flag("flag");
1503        let default = FlagValue::from(false);
1504        let context = MultiContextBuilder::new()
1505            .add_context(
1506                ContextBuilder::new("alice")
1507                    .build()
1508                    .expect("Failed to create context"),
1509            )
1510            .add_context(
1511                ContextBuilder::new("LaunchDarkly")
1512                    .kind("org")
1513                    .build()
1514                    .expect("Failed to create context"),
1515            )
1516            .build()
1517            .expect("Failed to create multi-context");
1518
1519        let value = FlagValue::from(false);
1520        let variation_index = 1;
1521        let reason = Reason::Fallthrough {
1522            in_experiment: false,
1523        };
1524        let eval_at = 1234;
1525
1526        let fallthrough_request = FeatureRequestEvent {
1527            base: BaseEvent::new(eval_at, context),
1528            key: flag.key.clone(),
1529            value: value.clone(),
1530            variation: Some(variation_index),
1531            default: default.clone(),
1532            version: Some(flag.version),
1533            reason: Some(reason),
1534            prereq_of: None,
1535            track_events: false,
1536            debug_events_until_date: None,
1537            sampling_ratio: flag.sampling_ratio,
1538            exclude_from_summaries: flag.exclude_from_summaries,
1539        };
1540
1541        summary.add(&fallthrough_request);
1542        assert!(!summary.is_empty());
1543        assert_eq!(summary.start_date, eval_at);
1544        assert_eq!(summary.end_date, eval_at);
1545
1546        let fallthrough_key = VariationKey {
1547            version: Some(flag.version),
1548            variation: Some(variation_index),
1549        };
1550
1551        let feature = summary.features.get(&flag.key);
1552        assert!(feature.is_some());
1553        let feature = feature.unwrap();
1554        assert_eq!(feature.default, default);
1555        assert_eq!(2, feature.context_kinds.len());
1556        assert!(feature.context_kinds.contains(&Kind::user()));
1557        assert!(feature
1558            .context_kinds
1559            .contains(&Kind::try_from("org").unwrap()));
1560
1561        let fallthrough_summary = feature.counters.get(&fallthrough_key);
1562        if let Some(VariationSummary { count: c, value: v }) = fallthrough_summary {
1563            assert_eq!(*c, 1);
1564            assert_eq!(*v, value);
1565        } else {
1566            panic!("Fallthrough summary is wrong type");
1567        }
1568
1569        summary.add(&fallthrough_request);
1570        let feature = summary
1571            .features
1572            .get(&flag.key)
1573            .expect("Failed to get expected feature.");
1574        let fallthrough_summary = feature
1575            .counters
1576            .get(&fallthrough_key)
1577            .expect("Failed to get counters");
1578        assert_eq!(fallthrough_summary.count, 2);
1579        assert_eq!(2, feature.context_kinds.len());
1580    }
1581
1582    #[test]
1583    fn event_factory_unknown_flags_do_not_track_events() {
1584        let event_factory = EventFactory::new(true);
1585        let context = ContextBuilder::new("bob")
1586            .build()
1587            .expect("Failed to create context");
1588        let detail = Detail {
1589            value: Some(FlagValue::from(false)),
1590            variation_index: Some(1),
1591            reason: Reason::Off,
1592        };
1593        let event =
1594            event_factory.new_unknown_flag_event("myFlag", context, detail, FlagValue::Bool(true));
1595
1596        if let InputEvent::FeatureRequest(event) = event {
1597            assert!(!event.track_events);
1598        } else {
1599            panic!("Event should be a feature request type");
1600        }
1601    }
1602
1603    // Test for flag.track-events
1604    #[test_case(true, true, false, Reason::Off, true, true)]
1605    #[test_case(true, false, false, Reason::Off, false, true)]
1606    #[test_case(false, true, false, Reason::Off, true, false)]
1607    #[test_case(false, false, false, Reason::Off, false, false)]
1608    // Test for flag.track_events_fallthrough
1609    #[test_case(true, false, true, Reason::Off, false, true)]
1610    #[test_case(true, false, true, Reason::Fallthrough { in_experiment: false }, true, true)]
1611    #[test_case(true, false, false, Reason::Fallthrough { in_experiment: false }, false, true)]
1612    #[test_case(false, false, true, Reason::Off, false, false)]
1613    #[test_case(false, false, true, Reason::Fallthrough { in_experiment: false }, true, true)]
1614    #[test_case(false, false, false, Reason::Fallthrough { in_experiment: false }, false, false)]
1615    // Test for Flagthrough.in_experiment
1616    #[test_case(true, false, false, Reason::Fallthrough { in_experiment: true }, true, true)]
1617    #[test_case(false, false, false, Reason::Fallthrough { in_experiment: true }, true, true)]
1618    fn event_factory_eval_tracks_events(
1619        event_factory_send_events: bool,
1620        flag_track_events: bool,
1621        flag_track_events_fallthrough: bool,
1622        reason: Reason,
1623        should_events_be_tracked: bool,
1624        should_include_reason: bool,
1625    ) {
1626        let event_factory = EventFactory::new(event_factory_send_events);
1627        let mut flag = basic_flag("myFlag");
1628        flag.track_events = flag_track_events;
1629        flag.track_events_fallthrough = flag_track_events_fallthrough;
1630
1631        let context = ContextBuilder::new("bob")
1632            .build()
1633            .expect("Failed to create context");
1634        let detail = Detail {
1635            value: Some(FlagValue::from(false)),
1636            variation_index: Some(1),
1637            reason,
1638        };
1639        let event = event_factory.new_eval_event(
1640            "myFlag",
1641            context,
1642            &flag,
1643            detail,
1644            FlagValue::Bool(true),
1645            None,
1646        );
1647
1648        if let InputEvent::FeatureRequest(event) = event {
1649            assert_eq!(event.track_events, should_events_be_tracked);
1650            assert_eq!(event.reason.is_some(), should_include_reason);
1651        } else {
1652            panic!("Event should be a feature request type");
1653        }
1654    }
1655
1656    #[test_case(true, 0, false, true, true)]
1657    #[test_case(true, 0, true, true, true)]
1658    #[test_case(true, 1, false, false, true)]
1659    #[test_case(true, 1, true, true, true)]
1660    #[test_case(false, 0, false, true, true)]
1661    #[test_case(false, 0, true, true, true)]
1662    #[test_case(false, 1, false, false, false)]
1663    #[test_case(false, 1, true, true, true)]
1664    fn event_factory_eval_tracks_events_for_rule_matches(
1665        event_factory_send_events: bool,
1666        rule_index: usize,
1667        rule_in_experiment: bool,
1668        should_events_be_tracked: bool,
1669        should_include_reason: bool,
1670    ) {
1671        let event_factory = EventFactory::new(event_factory_send_events);
1672        let flag: Flag = serde_json::from_value(json!({
1673          "key": "with_rule",
1674          "on": true,
1675          "targets": [],
1676          "prerequisites": [],
1677          "rules": [
1678            {
1679              "id": "rule-0",
1680              "clauses": [{
1681                "attribute": "key",
1682                "negate": false,
1683                "op": "matches",
1684                "values": ["do-track"]
1685              }],
1686              "trackEvents": true,
1687              "variation": 1
1688            },
1689            {
1690              "id": "rule-1",
1691              "clauses": [{
1692                "attribute": "key",
1693                "negate": false,
1694                "op": "matches",
1695                "values": ["no-track"]
1696              }],
1697              "trackEvents": false,
1698              "variation": 1
1699            }
1700          ],
1701          "fallthrough": {"variation": 0},
1702          "trackEventsFallthrough": false,
1703          "offVariation": 0,
1704          "clientSideAvailability": {
1705            "usingMobileKey": false,
1706            "usingEnvironmentId": false
1707          },
1708          "salt": "kosher",
1709          "version": 2,
1710          "variations": [false, true]
1711        }))
1712        .unwrap();
1713
1714        let context = ContextBuilder::new("do-track")
1715            .build()
1716            .expect("Failed to create context");
1717        let detail = Detail {
1718            value: Some(FlagValue::from(false)),
1719            variation_index: Some(1),
1720            reason: Reason::RuleMatch {
1721                rule_index,
1722                rule_id: format!("rule-{rule_index}"),
1723                in_experiment: rule_in_experiment,
1724            },
1725        };
1726        let event = event_factory.new_eval_event(
1727            "myFlag",
1728            context,
1729            &flag,
1730            detail,
1731            FlagValue::Bool(true),
1732            None,
1733        );
1734
1735        if let InputEvent::FeatureRequest(event) = event {
1736            assert_eq!(event.track_events, should_events_be_tracked);
1737            assert_eq!(event.reason.is_some(), should_include_reason);
1738        } else {
1739            panic!("Event should be a feature request type");
1740        }
1741    }
1742}