statsig_rust/event_logging/event_queue/
queued_gate_expo.rs

1use std::borrow::Cow;
2
3use crate::{
4    evaluation::evaluation_types::{ExtraExposureInfo, GateEvaluation},
5    event_logging::{
6        event_logger::ExposureTrigger,
7        exposure_sampling::{EvtSamplingDecision, ExposureSamplingKey},
8        exposure_utils::{get_metadata_with_details, get_statsig_metadata_with_sampling_decision},
9        statsig_event::StatsigEvent,
10        statsig_event_internal::{StatsigEventInternal, GATE_EXPOSURE_EVENT_NAME},
11    },
12    interned_string::InternedString,
13    user::{StatsigUserInternal, StatsigUserLoggable},
14    EvaluationDetails, SecondaryExposure,
15};
16
17use super::queued_event::{EnqueueOperation, QueuedEvent, QueuedExposure};
18
19pub struct EnqueueGateExpoOp<'a> {
20    pub user: &'a StatsigUserInternal<'a, 'a>,
21    pub queried_gate_name: &'a str,
22    pub evaluation: Option<Cow<'a, GateEvaluation>>,
23    pub details: EvaluationDetails,
24    pub trigger: ExposureTrigger,
25    pub exposure_time: u64,
26}
27
28impl EnqueueOperation for EnqueueGateExpoOp<'_> {
29    fn as_exposure(&self) -> Option<&impl QueuedExposure<'_>> {
30        Some(self)
31    }
32
33    fn into_queued_event(self, sampling_decision: EvtSamplingDecision) -> QueuedEvent {
34        let (
35            gate_name, //
36            rule_id,
37            value,
38            version,
39            override_config_name,
40            secondary_exposures,
41        ) = extract_from_cow(self.evaluation);
42
43        QueuedEvent::GateExposure(QueuedGateExposureEvent {
44            user: self.user.to_loggable(),
45            gate_name,
46            value,
47            exposure_time: self.exposure_time,
48            version,
49            rule_id,
50            secondary_exposures,
51            evaluation_details: self.details,
52            override_config_name,
53            exposure_trigger: self.trigger,
54            sampling_decision,
55        })
56    }
57}
58
59impl<'a> QueuedExposure<'a> for EnqueueGateExpoOp<'a> {
60    fn create_exposure_sampling_key(&self) -> ExposureSamplingKey {
61        let user_data = &self.user.user_ref.data;
62        let evaluation = self.evaluation.as_ref().map(|e| &e.base);
63        let value = self.evaluation.as_ref().is_some_and(|e| e.value);
64
65        ExposureSamplingKey::new(evaluation, user_data.as_ref(), value as u64)
66    }
67
68    fn get_rule_id_ref(&'a self) -> &'a str {
69        rule_id_ref(&self.evaluation)
70    }
71
72    fn get_extra_exposure_info_ref(&'a self) -> Option<&'a ExtraExposureInfo> {
73        self.evaluation.as_ref()?.base.exposure_info.as_ref()
74    }
75}
76
77pub struct QueuedGateExposureEvent {
78    pub user: StatsigUserLoggable,
79    pub gate_name: InternedString,
80    pub value: bool,
81    pub rule_id: InternedString,
82    pub secondary_exposures: Option<Vec<SecondaryExposure>>,
83    pub evaluation_details: EvaluationDetails,
84    pub version: Option<u32>,
85    pub exposure_trigger: ExposureTrigger,
86    pub sampling_decision: EvtSamplingDecision,
87    pub override_config_name: Option<String>,
88    pub exposure_time: u64,
89}
90
91impl QueuedGateExposureEvent {
92    pub fn into_statsig_event_internal(self) -> StatsigEventInternal {
93        let mut metadata = get_metadata_with_details(self.evaluation_details);
94        metadata.insert("gate".into(), self.gate_name.unperformant_to_string());
95        metadata.insert("gateValue".into(), self.value.to_string());
96
97        // This mostly occurs in the EventLogger bg thread, so it's ok to use unperformant_to_string
98        // todo: investigate how to avoid cloning the inner value entirely
99        metadata.insert("ruleID".into(), self.rule_id.unperformant_to_string());
100
101        if self.exposure_trigger == ExposureTrigger::Manual {
102            metadata.insert("isManualExposure".into(), "true".into());
103        }
104
105        if let Some(version) = self.version {
106            metadata.insert("configVersion".into(), version.to_string());
107        }
108
109        if let Some(override_config_name) = self.override_config_name {
110            metadata.insert("overrideConfigName".into(), override_config_name);
111        }
112
113        let statsig_metadata = get_statsig_metadata_with_sampling_decision(self.sampling_decision);
114
115        let event = StatsigEvent {
116            event_name: GATE_EXPOSURE_EVENT_NAME.into(),
117            value: None,
118            metadata: Some(metadata),
119            statsig_metadata: Some(statsig_metadata),
120        };
121
122        StatsigEventInternal::new(
123            self.exposure_time,
124            self.user,
125            event,
126            self.secondary_exposures,
127        )
128    }
129}
130
131type ExtractInfoResult = (
132    InternedString,
133    InternedString,
134    bool,
135    Option<u32>,
136    Option<String>,
137    Option<Vec<SecondaryExposure>>,
138);
139
140fn extract_from_cow(moo: Option<Cow<'_, GateEvaluation>>) -> ExtractInfoResult {
141    let moo = match moo {
142        Some(m) => m,
143        None => {
144            return (
145                InternedString::default(),
146                InternedString::default(),
147                false,
148                None,
149                None,
150                None,
151            )
152        }
153    };
154
155    match moo {
156        Cow::Borrowed(evaluation) => {
157            let name = evaluation.base.name.clone();
158            let rule_id = evaluation.base.rule_id.clone();
159            let value = evaluation.value;
160            let expo_info = evaluation.base.exposure_info.clone();
161            let secondary_exposures = evaluation.base.secondary_exposures.clone();
162
163            let version = expo_info.as_ref().and_then(|info| info.version);
164            let override_config_name = expo_info
165                .as_ref()
166                .and_then(|info| info.override_config_name.clone());
167
168            (
169                name,
170                rule_id,
171                value,
172                version,
173                override_config_name,
174                Some(secondary_exposures),
175            )
176        }
177        Cow::Owned(evaluation) => {
178            let name = evaluation.base.name;
179            let rule_id = evaluation.base.rule_id;
180            let value = evaluation.value;
181            let expo_info = evaluation.base.exposure_info;
182            let secondary_exposures = evaluation.base.secondary_exposures;
183
184            let version = expo_info.as_ref().and_then(|info| info.version);
185            let override_config_name = expo_info.and_then(|info| info.override_config_name.clone());
186
187            (
188                name,
189                rule_id,
190                value,
191                version,
192                override_config_name,
193                Some(secondary_exposures),
194            )
195        }
196    }
197}
198
199fn rule_id_ref<'a>(moo: &'a Option<Cow<'a, GateEvaluation>>) -> &'a str {
200    moo.as_ref().map_or("", |x| x.base.rule_id.as_str())
201}