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}