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