Skip to main content

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