statsig_rust/evaluation/
evaluator_context.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use crate::evaluation::dynamic_value::DynamicValue;
5use crate::evaluation::evaluator_result::EvaluatorResult;
6use crate::hashing::HashUtil;
7use crate::id_lists_adapter::IdList;
8use crate::interned_string::InternedString;
9use crate::specs_response::spec_types::{Rule, Spec, SpecsResponseFull};
10use crate::user::StatsigUserInternal;
11use crate::StatsigErr::StackOverflowError;
12use crate::{OverrideAdapter, Statsig, StatsigErr};
13
14const MAX_RECURSIVE_DEPTH: u16 = 300;
15
16type NestedGateMemo = HashMap<InternedString, (bool, Option<InternedString>)>;
18
19pub enum IdListResolution<'a> {
20 MapLookup(&'a HashMap<String, IdList>),
21 Callback(&'a dyn Fn(&str, &str) -> bool),
22}
23
24pub struct EvaluatorContext<'a> {
25 pub user: &'a StatsigUserInternal<'a, 'a>,
26 pub specs_data: &'a SpecsResponseFull,
27 pub id_list_resolver: IdListResolution<'a>,
28 pub hashing: &'a HashUtil,
29 pub result: EvaluatorResult,
30 pub nested_count: u16,
31 pub app_id: Option<&'a DynamicValue>,
32 pub override_adapter: Option<&'a Arc<dyn OverrideAdapter>>,
33 pub nested_gate_memo: NestedGateMemo,
34 pub should_user_third_party_parser: bool,
35 pub statsig: Option<&'a Statsig>,
36 pub disable_exposure_logging: bool,
37 pub gcir_hashes: Vec<u64>,
38}
39
40impl<'a> EvaluatorContext<'a> {
41 #[allow(clippy::too_many_arguments)]
42 pub fn new(
43 user: &'a StatsigUserInternal,
44 specs_data: &'a SpecsResponseFull,
45 id_list_resolver: IdListResolution<'a>,
46 hashing: &'a HashUtil,
47 app_id: Option<&'a DynamicValue>,
48 override_adapter: Option<&'a Arc<dyn OverrideAdapter>>,
49 should_user_third_party_parser: bool,
50 statsig: Option<&'a Statsig>,
51 disable_exposure_logging: bool,
52 ) -> Self {
53 let result = EvaluatorResult::default();
54
55 Self {
56 user,
57 specs_data,
58 id_list_resolver,
59 hashing,
60 app_id,
61 result,
62 override_adapter,
63 nested_count: 0,
64 nested_gate_memo: HashMap::new(),
65 should_user_third_party_parser,
66 statsig,
67 disable_exposure_logging,
68 gcir_hashes: Vec::new(),
69 }
70 }
71
72 pub fn reset_result(&mut self) {
73 self.nested_count = 0;
74 self.result = EvaluatorResult::default();
75 }
76
77 pub fn finalize_evaluation(&mut self, spec: &Spec, rule: Option<&Rule>) {
78 self.result.sampling_rate = rule.and_then(|r| r.sampling_rate);
79 self.result.forward_all_exposures = spec.forward_all_exposures;
80
81 if self.nested_count > 0 {
82 self.nested_count -= 1;
83 return;
84 }
85
86 if self.result.secondary_exposures.is_empty() {
87 return;
88 }
89
90 if self.result.undelegated_secondary_exposures.is_some() {
91 return;
92 }
93
94 self.result.undelegated_secondary_exposures = Some(self.result.secondary_exposures.clone());
95 }
96
97 pub fn prep_for_nested_evaluation(&mut self) -> Result<(), StatsigErr> {
98 self.nested_count += 1;
99
100 self.result.bool_value = false;
101 self.result.json_value = None;
102
103 if self.nested_count > MAX_RECURSIVE_DEPTH {
104 return Err(StackOverflowError);
105 }
106
107 Ok(())
108 }
109}