Skip to main content

statsig_rust/evaluation/
evaluator_context.rs

1use 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
16// (gate_name, (bool_value, rule_id))
17type 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}