statsig_rust/override_adapter/
statsig_local_override_adapter.rs

1use crate::{log_d, log_e, read_lock_or_return, write_lock_or_noop, OverrideAdapter, StatsigUser};
2use std::{collections::HashMap, sync::RwLock};
3
4use crate::evaluation::evaluator_result::EvaluatorResult;
5use crate::specs_response::spec_types::Spec;
6use serde_json::Value;
7
8enum ExperimentOverrides {
9    Value(HashMap<String, Value>),
10    GroupName(String),
11}
12
13#[derive(Default)]
14struct OverrideStore {
15    pub gate: HashMap<String, HashMap<String, bool>>,
16    pub config: HashMap<String, HashMap<String, HashMap<String, Value>>>,
17    pub experiment: HashMap<String, HashMap<String, ExperimentOverrides>>,
18    pub layer: HashMap<String, HashMap<String, HashMap<String, Value>>>,
19}
20
21const TAG: &str = stringify!(StatsigLocalOverrideAdapter);
22const LOCAL_OVERRIDE_REASON: &str = "LocalOverride";
23const NO_ID_OVERRIDE: &str = "__STATSIG_NO_ID__";
24
25lazy_static::lazy_static! {
26    static ref OVERRIDE_RULE_ID: String = "override".to_string();
27}
28
29#[derive(Default)]
30pub struct StatsigLocalOverrideAdapter {
31    store: RwLock<OverrideStore>,
32}
33
34impl StatsigLocalOverrideAdapter {
35    #[must_use]
36    pub fn new() -> Self {
37        Self::default()
38    }
39}
40
41impl OverrideAdapter for StatsigLocalOverrideAdapter {
42    fn get_gate_override(
43        &self,
44        user: &StatsigUser,
45        gate_name: &str,
46        result: &mut EvaluatorResult<'_>,
47    ) -> bool {
48        let store = read_lock_or_return!(TAG, self.store, false);
49
50        let gate_overrides = match store.gate.get(gate_name) {
51            Some(overrides) => overrides,
52            None => return false,
53        };
54
55        log_d!(TAG, "gate_overrides found for {}", gate_name);
56
57        find_override_for_user(
58            user,
59            gate_overrides,
60            |value, res| {
61                res.bool_value = *value;
62            },
63            result,
64        )
65    }
66
67    fn get_dynamic_config_override(
68        &self,
69        user: &StatsigUser,
70        dynamic_config_name: &str,
71        result: &mut EvaluatorResult<'_>,
72    ) -> bool {
73        let store = read_lock_or_return!(TAG, self.store, false);
74
75        let config_overrides = match store.config.get(dynamic_config_name) {
76            Some(overrides) => overrides,
77            None => return false,
78        };
79
80        find_override_for_user(
81            user,
82            config_overrides,
83            |value, res| {
84                res.json_value = Some(value.clone());
85            },
86            result,
87        )
88    }
89
90    fn get_experiment_override(
91        &self,
92        user: &StatsigUser,
93        experiment_name: &str,
94        result: &mut EvaluatorResult<'_>,
95        opt_spec: Option<&Spec>,
96    ) -> bool {
97        let store = read_lock_or_return!(TAG, self.store, false);
98
99        let experiment_overrides = match store.experiment.get(experiment_name) {
100            Some(overrides) => overrides,
101            None => return false,
102        };
103
104        find_override_for_user(
105            user,
106            experiment_overrides,
107            |override_value, res| match override_value {
108                ExperimentOverrides::Value(map) => {
109                    res.json_value = Some(map.clone());
110                }
111                ExperimentOverrides::GroupName(group_name) => {
112                    res.json_value = get_experiment_with_group_name(opt_spec, group_name);
113                }
114            },
115            result,
116        )
117    }
118
119    fn get_layer_override(
120        &self,
121        user: &StatsigUser,
122        layer_name: &str,
123        result: &mut EvaluatorResult<'_>,
124    ) -> bool {
125        let store = read_lock_or_return!(TAG, self.store, false);
126
127        let layer_overrides = match store.layer.get(layer_name) {
128            Some(overrides) => overrides,
129            None => return false,
130        };
131
132        find_override_for_user(
133            user,
134            layer_overrides,
135            |value, res| {
136                res.json_value = Some(value.clone());
137            },
138            result,
139        )
140    }
141
142    fn override_gate(&self, key: &str, value: bool, id: Option<&str>) {
143        let mut store = write_lock_or_noop!(TAG, self.store);
144        let id_str = id.unwrap_or(NO_ID_OVERRIDE);
145        store
146            .gate
147            .entry(key.to_string())
148            .or_default()
149            .insert(id_str.to_string(), value);
150    }
151
152    fn override_dynamic_config(&self, key: &str, value: HashMap<String, Value>, id: Option<&str>) {
153        let mut store = write_lock_or_noop!(TAG, self.store);
154        let id_str = id.unwrap_or(NO_ID_OVERRIDE);
155        store
156            .config
157            .entry(key.to_string())
158            .or_default()
159            .insert(id_str.to_string(), value);
160    }
161
162    fn override_experiment(&self, key: &str, value: HashMap<String, Value>, id: Option<&str>) {
163        let mut store = write_lock_or_noop!(TAG, self.store);
164        let id_str = id.unwrap_or(NO_ID_OVERRIDE);
165        store
166            .experiment
167            .entry(key.to_string())
168            .or_default()
169            .insert(id_str.to_string(), ExperimentOverrides::Value(value));
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(), 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.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.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<HashMap<String, Value>> {
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 rule.return_value.get_json();
376        }
377    }
378
379    None
380}