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}