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