Skip to main content

statsig_rust/sdk_event_emitter/
event_emitter.rs

1use crate::{
2    log_e,
3    sdk_event_emitter::{SdkEvent, SdkEventCode},
4    statsig_types::{DynamicConfig, Experiment, Layer},
5    Statsig,
6};
7use dashmap::DashMap;
8use std::ops::Deref;
9
10const TAG: &str = "SdkEventEmitter";
11
12struct Listener {
13    sub_id_value: String,
14    callback: Box<dyn Fn(SdkEvent) + Send + Sync>,
15}
16
17#[derive(Clone)]
18pub struct SubscriptionID {
19    value: String,
20    event: String,
21}
22
23impl SubscriptionID {
24    pub fn new(event: &str) -> Self {
25        Self {
26            value: uuid::Uuid::new_v4().to_string(),
27            event: event.to_string(),
28        }
29    }
30
31    pub fn error() -> Self {
32        Self {
33            value: "ERROR".to_string(),
34            event: "ERROR".to_string(),
35        }
36    }
37
38    pub fn decode(s: &str) -> Option<Self> {
39        let parts: Vec<&str> = s.split('@').collect();
40        if parts.len() != 2 {
41            return None;
42        }
43
44        Some(Self {
45            value: parts[0].to_string(),
46            event: parts[1].to_string(),
47        })
48    }
49
50    pub fn encode(self) -> String {
51        let mut encoded = self.value;
52        encoded.push('@');
53        encoded.push_str(&self.event);
54        encoded
55    }
56}
57
58#[derive(Default)]
59pub struct SdkEventEmitter {
60    listeners: DashMap<u8, Vec<Listener>>,
61}
62
63impl SdkEventEmitter {
64    pub fn subscribe<F>(&self, event: &str, callback: F) -> SubscriptionID
65    where
66        F: Fn(SdkEvent) + Send + Sync + 'static,
67    {
68        let code = SdkEventCode::from_name(event).as_raw();
69        if code == 0 {
70            log_e!(TAG, "Invalid event name: {}", event);
71            return SubscriptionID::error();
72        }
73
74        let sub_id = SubscriptionID::new(event);
75
76        self.listeners.entry(code).or_default().push(Listener {
77            sub_id_value: sub_id.value.clone(),
78            callback: Box::new(callback),
79        });
80
81        sub_id
82    }
83
84    pub fn unsubscribe(&self, event: &str) {
85        let code = SdkEventCode::from_name(event).as_raw();
86        self.listeners.remove(&code);
87    }
88
89    pub fn unsubscribe_by_id(&self, subscription_id: &SubscriptionID) {
90        let code = SdkEventCode::from_name(&subscription_id.event).as_raw();
91        let mut listeners = match self.listeners.get_mut(&code) {
92            Some(listeners) => listeners,
93            None => return,
94        };
95
96        listeners.retain(|listener| listener.sub_id_value != subscription_id.value);
97    }
98
99    pub fn unsubscribe_all(&self) {
100        self.listeners.clear();
101    }
102
103    pub(crate) fn emit(&self, event: SdkEvent) {
104        let all_code = SdkEventCode::from_name(SdkEvent::ALL).as_raw();
105        self.emit_to_listeners(&event, self.listeners.get(&all_code).as_deref());
106
107        let event_code = event.get_code().as_raw();
108        self.emit_to_listeners(&event, self.listeners.get(&event_code).as_deref());
109    }
110
111    fn emit_to_listeners(&self, event: &SdkEvent, listeners: Option<&Vec<Listener>>) {
112        let listeners = match listeners {
113            Some(listeners) => listeners,
114            None => return,
115        };
116
117        listeners
118            .iter()
119            .for_each(|listener| (listener.callback)(event.clone()));
120    }
121}
122
123impl Deref for Statsig {
124    type Target = SdkEventEmitter;
125
126    fn deref(&self) -> &Self::Target {
127        &self.event_emitter
128    }
129}
130
131impl Statsig {
132    pub(crate) fn emit_gate_evaluated(
133        &self,
134        gate_name: &str,
135        rule_id: &str,
136        value: bool,
137        reason: &str,
138    ) {
139        self.emit(SdkEvent::GateEvaluated {
140            gate_name,
141            rule_id,
142            value,
143            reason,
144        });
145    }
146
147    pub(crate) fn emit_dynamic_config_evaluated(&self, config: &DynamicConfig) {
148        self.emit(SdkEvent::DynamicConfigEvaluated {
149            config_name: config.name.as_str(),
150            reason: config.details.reason.as_str(),
151            rule_id: Some(config.rule_id.as_str()),
152            value: config.__evaluation.as_ref().map(|e| &e.value),
153        });
154    }
155
156    pub(crate) fn emit_experiment_evaluated(&self, experiment: &Experiment) {
157        self.emit(SdkEvent::ExperimentEvaluated {
158            experiment_name: experiment.name.as_str(),
159            reason: experiment.details.reason.as_str(),
160            rule_id: Some(experiment.rule_id.as_str()),
161            value: experiment.__evaluation.as_ref().map(|e| &e.value),
162            group_name: experiment.group_name.as_deref(),
163        });
164    }
165
166    pub(crate) fn emit_layer_evaluated(&self, layer: &Layer) {
167        self.emit(SdkEvent::LayerEvaluated {
168            layer_name: layer.name.as_str(),
169            reason: layer.details.reason.as_str(),
170            rule_id: Some(layer.rule_id.as_str()),
171        });
172    }
173}
174
175#[cfg(feature = "ffi-support")]
176impl Statsig {
177    pub(crate) fn emit_gate_evaluated_parts(
178        &self,
179        gate_name: &str,
180        reason: &str,
181        eval_result: Option<&crate::evaluation::evaluator_result::EvaluatorResult>,
182    ) {
183        let mut rule_id = None;
184        let mut value = false;
185
186        if let Some(eval) = eval_result {
187            rule_id = eval.rule_id.as_ref().map(|r| r.as_str());
188            value = eval.bool_value;
189        }
190
191        self.emit(SdkEvent::GateEvaluated {
192            gate_name,
193            rule_id: rule_id.unwrap_or_default(),
194            value,
195            reason,
196        });
197    }
198
199    pub(crate) fn emit_dynamic_config_evaluated_parts(
200        &self,
201        config_name: &str,
202        reason: &str,
203        eval_result: Option<&crate::evaluation::evaluator_result::EvaluatorResult>,
204    ) {
205        let mut rule_id = None;
206        let mut value = None;
207
208        if let Some(eval) = eval_result {
209            rule_id = eval.rule_id.as_ref().map(|r| r.as_str());
210            value = eval.json_value.as_ref();
211        }
212
213        self.emit(SdkEvent::DynamicConfigEvaluated {
214            config_name,
215            reason,
216            rule_id,
217            value,
218        });
219    }
220
221    pub(crate) fn emit_experiment_evaluated_parts(
222        &self,
223        experiment_name: &str,
224        reason: &str,
225        eval_result: Option<&crate::evaluation::evaluator_result::EvaluatorResult>,
226    ) {
227        let mut rule_id = None;
228        let mut value = None;
229        let mut group_name = None;
230
231        if let Some(eval) = eval_result {
232            rule_id = eval.rule_id.as_ref().map(|r| r.as_str());
233            value = eval.json_value.as_ref();
234            group_name = eval.group_name.as_ref().map(|g| g.as_str());
235        }
236
237        self.emit(SdkEvent::ExperimentEvaluated {
238            experiment_name,
239            reason,
240            rule_id,
241            value,
242            group_name,
243        });
244    }
245
246    pub(crate) fn emit_layer_evaluated_parts(
247        &self,
248        layer_name: &str,
249        reason: &str,
250        eval_result: Option<&crate::evaluation::evaluator_result::EvaluatorResult>,
251    ) {
252        let mut rule_id = None;
253
254        if let Some(eval) = eval_result {
255            rule_id = eval.rule_id.as_ref().map(|r| r.as_str());
256        }
257
258        self.emit(SdkEvent::LayerEvaluated {
259            layer_name,
260            reason,
261            rule_id,
262        });
263    }
264}