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