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