statsig_rust/sdk_event_emitter/
event_emitter.rs1use 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}