statsig_rust/event_logging/event_queue/
queued_layer_param_expo.rs

1use crate::{
2    evaluation::evaluation_types::ExtraExposureInfo,
3    event_logging::{
4        event_logger::ExposureTrigger,
5        exposure_sampling::{EvtSamplingDecision, ExposureSamplingKey},
6        exposure_utils::{get_metadata_with_details, get_statsig_metadata_with_sampling_decision},
7        statsig_event::StatsigEvent,
8        statsig_event_internal::{StatsigEventInternal, LAYER_EXPOSURE_EVENT_NAME},
9    },
10    hashing::ahash_str,
11    statsig_types::Layer,
12    user::StatsigUserLoggable,
13    EvaluationDetails, SecondaryExposure,
14};
15
16use super::queued_event::{EnqueueOperation, QueuedEvent, QueuedExposure};
17
18pub enum EnqueueLayerParamExpoOp<'a> {
19    LayerRef(u64, &'a Layer, &'a str, ExposureTrigger),
20    LayerOwned(u64, Box<Layer>, String, ExposureTrigger),
21}
22
23impl<'a> EnqueueLayerParamExpoOp<'a> {
24    fn get_layer_ref(&'a self) -> &'a Layer {
25        match self {
26            EnqueueLayerParamExpoOp::LayerRef(_, layer, _, _) => layer,
27            EnqueueLayerParamExpoOp::LayerOwned(_, layer, _, _) => layer,
28        }
29    }
30
31    fn get_parameter_name_ref(&'a self) -> &'a str {
32        match self {
33            EnqueueLayerParamExpoOp::LayerRef(_, _, parameter_name, _) => parameter_name,
34            EnqueueLayerParamExpoOp::LayerOwned(_, _, parameter_name, _) => parameter_name.as_str(),
35        }
36    }
37}
38
39impl EnqueueOperation for EnqueueLayerParamExpoOp<'_> {
40    fn as_exposure(&self) -> Option<&impl QueuedExposure> {
41        Some(self)
42    }
43
44    fn into_queued_event(self, sampling_decision: EvtSamplingDecision) -> QueuedEvent {
45        let event = match self {
46            EnqueueLayerParamExpoOp::LayerRef(exposure_time, layer, parameter_name, trigger) => {
47                extract_from_layer_ref(
48                    exposure_time,
49                    layer,
50                    parameter_name,
51                    trigger,
52                    sampling_decision,
53                )
54            }
55            EnqueueLayerParamExpoOp::LayerOwned(exposure_time, layer, parameter_name, trigger) => {
56                extract_from_layer_owned(
57                    exposure_time,
58                    layer,
59                    parameter_name,
60                    trigger,
61                    sampling_decision,
62                )
63            }
64        };
65
66        QueuedEvent::LayerParamExposure(event)
67    }
68}
69
70impl<'a> QueuedExposure<'a> for EnqueueLayerParamExpoOp<'a> {
71    fn create_exposure_sampling_key(&self) -> ExposureSamplingKey {
72        let layer = self.get_layer_ref();
73
74        let user_data = &layer.__user.data;
75        let evaluation = layer.__evaluation.as_ref().map(|e| &e.base);
76
77        // todo: use Cow and pre-hash the parameter name
78        let pname = self.get_parameter_name_ref();
79        let pname_hash = ahash_str(pname);
80
81        ExposureSamplingKey::new(evaluation, user_data.as_ref(), pname_hash)
82    }
83
84    fn get_rule_id_ref(&'a self) -> &'a str {
85        &self.get_layer_ref().rule_id
86    }
87
88    fn get_extra_exposure_info_ref(&'a self) -> Option<&'a ExtraExposureInfo> {
89        self.get_layer_ref()
90            .__evaluation
91            .as_ref()?
92            .base
93            .exposure_info
94            .as_ref()
95    }
96}
97
98pub struct QueuedLayerParamExposureEvent {
99    pub user: StatsigUserLoggable,
100    pub layer_name: String,
101    pub rule_id: String,
102    pub parameter_name: String,
103    pub secondary_exposures: Option<Vec<SecondaryExposure>>,
104    pub evaluation_details: EvaluationDetails,
105    pub version: Option<u32>,
106    pub exposure_trigger: ExposureTrigger,
107    pub sampling_decision: EvtSamplingDecision,
108    pub override_config_name: Option<String>,
109    pub is_explicit: bool,
110    pub allocated_experiment: Option<String>,
111    pub exposure_time: u64,
112}
113
114impl QueuedLayerParamExposureEvent {
115    pub fn into_statsig_event_internal(self) -> StatsigEventInternal {
116        let mut metadata = get_metadata_with_details(self.evaluation_details);
117        metadata.insert("config".into(), self.layer_name);
118        metadata.insert("ruleID".into(), self.rule_id);
119        metadata.insert(
120            "allocatedExperiment".into(),
121            self.allocated_experiment.unwrap_or_default(),
122        );
123        metadata.insert("parameterName".into(), self.parameter_name);
124        metadata.insert("isExplicitParameter".into(), self.is_explicit.to_string());
125
126        if let Some(version) = self.version {
127            metadata.insert("configVersion".into(), version.to_string());
128        }
129
130        if self.exposure_trigger == ExposureTrigger::Manual {
131            metadata.insert("isManualExposure".into(), "true".into());
132        }
133
134        if let Some(override_config_name) = self.override_config_name {
135            metadata.insert("overrideConfigName".into(), override_config_name);
136        }
137
138        let statsig_metadata = get_statsig_metadata_with_sampling_decision(self.sampling_decision);
139
140        let event = StatsigEvent {
141            event_name: LAYER_EXPOSURE_EVENT_NAME.into(),
142            value: None,
143            metadata: Some(metadata),
144            statsig_metadata: Some(statsig_metadata),
145        };
146
147        StatsigEventInternal::new(
148            self.exposure_time,
149            self.user,
150            event,
151            Some(self.secondary_exposures.unwrap_or_default()),
152        )
153    }
154}
155
156type ExtractFromEvaluationResult = (
157    bool,
158    Option<String>,
159    Option<Vec<SecondaryExposure>>,
160    Option<u32>,
161    Option<String>,
162);
163
164fn extract_exposure_info(layer: &Layer, parameter_name: &String) -> ExtractFromEvaluationResult {
165    let evaluation = match layer.__evaluation.as_ref() {
166        Some(eval) => eval,
167        None => return (false, None, None, None, None),
168    };
169
170    let is_explicit = evaluation.explicit_parameters.contains(parameter_name);
171    let secondary_exposures;
172    let mut allocated_experiment = None;
173
174    if is_explicit {
175        allocated_experiment = evaluation.allocated_experiment_name.clone();
176        secondary_exposures = Some(evaluation.base.secondary_exposures.clone());
177    } else {
178        secondary_exposures = evaluation.undelegated_secondary_exposures.clone();
179    }
180
181    // version might be on the top level or the exposure info
182    let mut version = layer.__version;
183    let mut override_config_name = None;
184
185    if let Some(exposure_info) = evaluation.base.exposure_info.as_ref() {
186        version = exposure_info.version;
187        override_config_name = exposure_info.override_config_name.clone();
188    }
189
190    (
191        is_explicit,
192        allocated_experiment,
193        secondary_exposures,
194        version,
195        override_config_name,
196    )
197}
198
199fn extract_from_layer_ref(
200    exposure_time: u64,
201    layer: &Layer,
202    param_name: &str,
203    trigger: ExposureTrigger,
204    sampling_decision: EvtSamplingDecision,
205) -> QueuedLayerParamExposureEvent {
206    let parameter_name = param_name.to_string();
207    let (is_explicit, allocated_experiment, secondary_exposures, version, override_config_name) =
208        extract_exposure_info(layer, &parameter_name);
209
210    QueuedLayerParamExposureEvent {
211        exposure_time,
212        user: layer.__user.clone(),
213        layer_name: layer.name.clone(),
214        rule_id: layer.rule_id.clone(),
215        parameter_name,
216        exposure_trigger: trigger,
217        evaluation_details: layer.details.clone(),
218        version,
219        sampling_decision,
220        override_config_name,
221        secondary_exposures,
222        is_explicit,
223        allocated_experiment,
224    }
225}
226
227fn extract_from_layer_owned(
228    exposure_time: u64,
229    layer: Box<Layer>,
230    parameter_name: String,
231    trigger: ExposureTrigger,
232    sampling_decision: EvtSamplingDecision,
233) -> QueuedLayerParamExposureEvent {
234    let (is_explicit, allocated_experiment, secondary_exposures, version, override_config_name) =
235        extract_exposure_info(&layer, &parameter_name);
236
237    QueuedLayerParamExposureEvent {
238        exposure_time,
239        user: layer.__user,
240        layer_name: layer.name,
241        rule_id: layer.rule_id,
242        parameter_name,
243        exposure_trigger: trigger,
244        evaluation_details: layer.details,
245        version,
246        sampling_decision,
247        override_config_name,
248        secondary_exposures,
249        is_explicit,
250        allocated_experiment,
251    }
252}