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 unit_id_type = self
65            .evaluation
66            .as_ref()
67            .and_then(|eval| eval.id_type.as_ref())
68            .map(|id| id.as_str());
69        let value = self.evaluation.as_ref().is_some_and(|e| e.value);
70        let additional_hash = value as u64;
71
72        ExposureSamplingKey::new(evaluation, user_data, additional_hash, unit_id_type)
73    }
74
75    fn get_rule_id_ref(&'a self) -> &'a str {
76        rule_id_ref(&self.evaluation)
77    }
78
79    fn get_extra_exposure_info_ref(&'a self) -> Option<&'a ExtraExposureInfo> {
80        self.evaluation.as_ref()?.base.exposure_info.as_ref()
81    }
82}
83
84pub struct QueuedGateExposureEvent {
85    pub user: StatsigUserLoggable,
86    pub gate_name: InternedString,
87    pub value: bool,
88    pub rule_id: InternedString,
89    pub secondary_exposures: Option<Vec<SecondaryExposure>>,
90    pub evaluation_details: EvaluationDetails,
91    pub version: Option<u32>,
92    pub exposure_trigger: ExposureTrigger,
93    pub sampling_decision: EvtSamplingDecision,
94    pub override_config_name: Option<InternedString>,
95    pub exposure_time: u64,
96}
97
98impl QueuedGateExposureEvent {
99    pub fn into_statsig_event_internal(self) -> StatsigEventInternal {
100        let mut metadata = get_metadata_with_details(self.evaluation_details);
101        metadata.insert("gate".into(), self.gate_name.unperformant_to_string());
102        metadata.insert("gateValue".into(), self.value.to_string());
103
104        // This mostly occurs in the EventLogger bg thread, so it's ok to use unperformant_to_string
105        // todo: investigate how to avoid cloning the inner value entirely
106        metadata.insert("ruleID".into(), self.rule_id.unperformant_to_string());
107
108        if self.exposure_trigger == ExposureTrigger::Manual {
109            metadata.insert("isManualExposure".into(), "true".into());
110        }
111
112        if let Some(version) = self.version {
113            metadata.insert("configVersion".into(), version.to_string());
114        }
115
116        if let Some(override_config_name) = self.override_config_name {
117            metadata.insert(
118                "overrideConfigName".into(),
119                override_config_name.unperformant_to_string(),
120            );
121        }
122
123        let statsig_metadata = get_statsig_metadata_with_sampling_decision(self.sampling_decision);
124
125        let event = StatsigEvent {
126            event_name: GATE_EXPOSURE_EVENT_NAME.into(),
127            value: None,
128            metadata: Some(string_metadata_to_value_metadata(metadata)),
129            statsig_metadata: Some(statsig_metadata),
130        };
131
132        StatsigEventInternal::new(
133            self.exposure_time,
134            self.user,
135            event,
136            self.secondary_exposures,
137        )
138    }
139}
140
141type ExtractInfoResult = (
142    InternedString,
143    InternedString,
144    bool,
145    Option<u32>,
146    Option<InternedString>,
147    Option<Vec<SecondaryExposure>>,
148);
149
150fn extract_from_cow(moo: Option<Cow<'_, GateEvaluation>>) -> ExtractInfoResult {
151    let moo = match moo {
152        Some(m) => m,
153        None => {
154            return (
155                InternedString::default(),
156                InternedString::default(),
157                false,
158                None,
159                None,
160                None,
161            )
162        }
163    };
164
165    match moo {
166        Cow::Borrowed(evaluation) => {
167            let name = evaluation.base.name.clone();
168            let rule_id = evaluation.base.rule_id.clone();
169            let value = evaluation.value;
170            let expo_info = evaluation.base.exposure_info.clone();
171            let secondary_exposures = evaluation.base.secondary_exposures.clone();
172
173            let version = expo_info.as_ref().and_then(|info| info.version);
174            let override_config_name = expo_info
175                .as_ref()
176                .and_then(|info| info.override_config_name.clone());
177
178            (
179                name,
180                rule_id,
181                value,
182                version,
183                override_config_name,
184                Some(secondary_exposures),
185            )
186        }
187        Cow::Owned(evaluation) => {
188            let name = evaluation.base.name;
189            let rule_id = evaluation.base.rule_id;
190            let value = evaluation.value;
191            let expo_info = evaluation.base.exposure_info;
192            let secondary_exposures = evaluation.base.secondary_exposures;
193
194            let version = expo_info.as_ref().and_then(|info| info.version);
195            let override_config_name = expo_info.and_then(|info| info.override_config_name.clone());
196
197            (
198                name,
199                rule_id,
200                value,
201                version,
202                override_config_name,
203                Some(secondary_exposures),
204            )
205        }
206    }
207}
208
209fn rule_id_ref<'a>(moo: &'a Option<Cow<'a, GateEvaluation>>) -> &'a str {
210    moo.as_ref().map_or("", |x| x.base.rule_id.as_str())
211}