statsig_rust/override_adapter/
statsig_local_override_adapter.rs

1use crate::evaluation::dynamic_returnable::DynamicReturnable;
2use crate::evaluation::evaluator_result::EvaluatorResult;
3use crate::event_logging::exposable_string::ExposableString;
4use crate::specs_response::spec_types::Spec;
5use crate::{log_d, read_lock_or_return, write_lock_or_noop, OverrideAdapter, StatsigUser};
6use parking_lot::RwLock;
7use serde_json::Value;
8use std::collections::HashMap;
9
10enum ExperimentOverrides {
11    Value(DynamicReturnable),
12    GroupName(String),
13}
14
15#[derive(Default)]
16struct OverrideStore {
17    pub gate: HashMap<String, HashMap<String, bool>>,
18    pub config: HashMap<String, HashMap<String, DynamicReturnable>>,
19    pub experiment: HashMap<String, HashMap<String, ExperimentOverrides>>,
20    pub layer: HashMap<String, HashMap<String, DynamicReturnable>>,
21}
22
23const TAG: &str = stringify!(StatsigLocalOverrideAdapter);
24const LOCAL_OVERRIDE_REASON: &str = "LocalOverride";
25const NO_ID_OVERRIDE: &str = "__STATSIG_NO_ID__";
26
27lazy_static::lazy_static! {
28    static ref OVERRIDE_RULE_ID: ExposableString = ExposableString::from_str_ref("override");
29}
30
31#[derive(Default)]
32pub struct StatsigLocalOverrideAdapter {
33    store: RwLock<OverrideStore>,
34}
35
36impl StatsigLocalOverrideAdapter {
37    #[must_use]
38    pub fn new() -> Self {
39        Self::default()
40    }
41}
42
43impl OverrideAdapter for StatsigLocalOverrideAdapter {
44    fn get_gate_override(
45        &self,
46        user: &StatsigUser,
47        gate_name: &str,
48        result: &mut EvaluatorResult<'_>,
49    ) -> bool {
50        let store = read_lock_or_return!(TAG, self.store, false);
51
52        let gate_overrides = match store.gate.get(gate_name) {
53            Some(overrides) => overrides,
54            None => return false,
55        };
56
57        log_d!(TAG, "gate_overrides found for {}", gate_name);
58
59        find_override_for_user(
60            user,
61            gate_overrides,
62            |value, res| {
63                res.bool_value = *value;
64            },
65            result,
66        )
67    }
68
69    fn get_dynamic_config_override(
70        &self,
71        user: &StatsigUser,
72        dynamic_config_name: &str,
73        result: &mut EvaluatorResult<'_>,
74    ) -> bool {
75        let store = read_lock_or_return!(TAG, self.store, false);
76
77        let config_overrides = match store.config.get(dynamic_config_name) {
78            Some(overrides) => overrides,
79            None => return false,
80        };
81
82        find_override_for_user(
83            user,
84            config_overrides,
85            |value, res| {
86                res.json_value = Some(value.clone());
87            },
88            result,
89        )
90    }
91
92    fn get_experiment_override(
93        &self,
94        user: &StatsigUser,
95        experiment_name: &str,
96        result: &mut EvaluatorResult<'_>,
97        opt_spec: Option<&Spec>,
98    ) -> bool {
99        let store = read_lock_or_return!(TAG, self.store, false);
100
101        let experiment_overrides = match store.experiment.get(experiment_name) {
102            Some(overrides) => overrides,
103            None => return false,
104        };
105
106        find_override_for_user(
107            user,
108            experiment_overrides,
109            |override_value, res| match override_value {
110                ExperimentOverrides::Value(map) => {
111                    res.json_value = Some(map.clone());
112                }
113                ExperimentOverrides::GroupName(group_name) => {
114                    res.json_value = get_experiment_with_group_name(opt_spec, group_name);
115                }
116            },
117            result,
118        )
119    }
120
121    fn get_layer_override(
122        &self,
123        user: &StatsigUser,
124        layer_name: &str,
125        result: &mut EvaluatorResult<'_>,
126    ) -> bool {
127        let store = read_lock_or_return!(TAG, self.store, false);
128
129        let layer_overrides = match store.layer.get(layer_name) {
130            Some(overrides) => overrides,
131            None => return false,
132        };
133
134        find_override_for_user(
135            user,
136            layer_overrides,
137            |value, res| {
138                res.json_value = Some(value.clone());
139            },
140            result,
141        )
142    }
143
144    fn override_gate(&self, key: &str, value: bool, id: Option<&str>) {
145        let mut store = write_lock_or_noop!(TAG, self.store);
146        let id_str = id.unwrap_or(NO_ID_OVERRIDE);
147        store
148            .gate
149            .entry(key.to_string())
150            .or_default()
151            .insert(id_str.to_string(), value);
152    }
153
154    fn override_dynamic_config(&self, key: &str, value: HashMap<String, Value>, id: Option<&str>) {
155        let mut store = write_lock_or_noop!(TAG, self.store);
156        let id_str = id.unwrap_or(NO_ID_OVERRIDE);
157        store
158            .config
159            .entry(key.to_string())
160            .or_default()
161            .insert(id_str.to_string(), DynamicReturnable::from_map(value));
162    }
163
164    fn override_experiment(&self, key: &str, value: HashMap<String, Value>, id: Option<&str>) {
165        let mut store = write_lock_or_noop!(TAG, self.store);
166        let id_str = id.unwrap_or(NO_ID_OVERRIDE);
167        store.experiment.entry(key.to_string()).or_default().insert(
168            id_str.to_string(),
169            ExperimentOverrides::Value(DynamicReturnable::from_map(value)),
170        );
171    }
172
173    fn override_experiment_by_group_name(&self, key: &str, group_name: &str, id: Option<&str>) {
174        let mut store = write_lock_or_noop!(TAG, self.store);
175        let id_str = id.unwrap_or(NO_ID_OVERRIDE);
176        store.experiment.entry(key.to_string()).or_default().insert(
177            id_str.to_string(),
178            ExperimentOverrides::GroupName(group_name.to_string()),
179        );
180    }
181
182    fn override_layer(&self, key: &str, value: HashMap<String, Value>, id: Option<&str>) {
183        let mut store = write_lock_or_noop!(TAG, self.store);
184        let id_str = id.unwrap_or(NO_ID_OVERRIDE);
185        store
186            .layer
187            .entry(key.to_string())
188            .or_default()
189            .insert(id_str.to_string(), DynamicReturnable::from_map(value));
190    }
191
192    fn remove_gate_override(&self, key: &str, id: Option<&str>) {
193        let mut store = write_lock_or_noop!(TAG, self.store);
194        match id {
195            None => {
196                store.gate.remove(key);
197            }
198            Some(id_str) => {
199                if let Some(overrides) = store.gate.get_mut(key) {
200                    overrides.remove(&id_str.to_string());
201                }
202            }
203        }
204    }
205
206    fn remove_dynamic_config_override(&self, key: &str, id: Option<&str>) {
207        let mut store = write_lock_or_noop!(TAG, self.store);
208        match id {
209            None => {
210                store.config.remove(key);
211            }
212            Some(id_str) => {
213                if let Some(overrides) = store.config.get_mut(key) {
214                    overrides.remove(&id_str.to_string());
215                }
216            }
217        }
218    }
219
220    fn remove_experiment_override(&self, key: &str, id: Option<&str>) {
221        let mut store = write_lock_or_noop!(TAG, self.store);
222        match id {
223            None => {
224                store.experiment.remove(key);
225            }
226            Some(id_str) => {
227                if let Some(overrides) = store.experiment.get_mut(key) {
228                    overrides.remove(&id_str.to_string());
229                }
230            }
231        }
232    }
233
234    fn remove_layer_override(&self, key: &str, id: Option<&str>) {
235        let mut store = write_lock_or_noop!(TAG, self.store);
236        match id {
237            None => {
238                store.layer.remove(key);
239            }
240            Some(id_str) => {
241                if let Some(overrides) = store.layer.get_mut(key) {
242                    overrides.remove(&id_str.to_string());
243                }
244            }
245        }
246    }
247
248    fn remove_all_overrides(&self) {
249        let mut store = write_lock_or_noop!(TAG, self.store);
250        store.gate.clear();
251        store.config.clear();
252        store.experiment.clear();
253        store.layer.clear();
254    }
255}
256
257fn find_override_for_user<T, F>(
258    user: &StatsigUser,
259    overrides: &HashMap<String, T>,
260    apply_override: F,
261    result: &mut EvaluatorResult<'_>,
262) -> bool
263where
264    F: Fn(&T, &mut EvaluatorResult<'_>),
265{
266    if check_user_id_override(user, overrides, &apply_override, result) {
267        return true;
268    }
269
270    if check_custom_ids_override(user, overrides, &apply_override, result) {
271        return true;
272    }
273
274    check_default_override(overrides, apply_override, result)
275}
276
277fn mark_result_as_override(result: &mut EvaluatorResult<'_>) {
278    result.override_reason = Some(LOCAL_OVERRIDE_REASON);
279    result.rule_id = Some(&OVERRIDE_RULE_ID);
280}
281
282fn check_default_override<T, F>(
283    overrides: &HashMap<String, T>,
284    apply_override: F,
285    result: &mut EvaluatorResult<'_>,
286) -> bool
287where
288    F: Fn(&T, &mut EvaluatorResult<'_>),
289{
290    if let Some(override_value) = overrides.get(NO_ID_OVERRIDE) {
291        log_d!(TAG, "default override found");
292        apply_override(override_value, result);
293        mark_result_as_override(result);
294        return true;
295    }
296    false
297}
298
299fn check_user_id_override<T, F>(
300    user: &StatsigUser,
301    overrides: &HashMap<String, T>,
302    apply_override: F,
303    result: &mut EvaluatorResult<'_>,
304) -> bool
305where
306    F: Fn(&T, &mut EvaluatorResult<'_>),
307{
308    let user_id = match &user.data.user_id {
309        Some(id) => id,
310        None => return false,
311    };
312
313    let id_string = match &user_id.string_value {
314        Some(s) => &s.value,
315        None => return false,
316    };
317
318    let override_value = match overrides.get(id_string.as_str()) {
319        Some(v) => v,
320        None => return false,
321    };
322
323    log_d!(TAG, "override found for user ID {}", id_string);
324    apply_override(override_value, result);
325    mark_result_as_override(result);
326    true
327}
328
329fn check_custom_ids_override<T, F>(
330    user: &StatsigUser,
331    overrides: &HashMap<String, T>,
332    apply_override: F,
333    result: &mut EvaluatorResult<'_>,
334) -> bool
335where
336    F: Fn(&T, &mut EvaluatorResult<'_>),
337{
338    let custom_ids = match &user.data.custom_ids {
339        Some(ids) => ids,
340        None => return false,
341    };
342
343    for custom_id_value in custom_ids.values() {
344        let id_string = match &custom_id_value.string_value {
345            Some(s) => &s.value,
346            None => continue,
347        };
348
349        let override_value = match overrides.get(id_string.as_str()) {
350            Some(v) => v,
351            None => continue,
352        };
353
354        log_d!(TAG, "override found for custom ID {}", id_string);
355        apply_override(override_value, result);
356        mark_result_as_override(result);
357        return true;
358    }
359
360    false
361}
362
363fn get_experiment_with_group_name(
364    opt_spec: Option<&Spec>,
365    group_name: &str,
366) -> Option<DynamicReturnable> {
367    let spec = opt_spec?;
368
369    for rule in &spec.rules {
370        let rule_group_name = match &rule.group_name {
371            Some(rule_group_name) => rule_group_name,
372            None => continue,
373        };
374
375        if rule_group_name == group_name {
376            return Some(rule.return_value.clone());
377        }
378    }
379
380    None
381}