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