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