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}