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, SecondaryExposure, Statsig, StatsigErr};
13
14const MAX_RECURSIVE_DEPTH: u16 = 300;
15
16// (gate_name, (bool_value, rule_id, secondary_exposures))
17type 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 = EvaluatorResult::default();
76    }
77
78    pub fn finalize_evaluation(&mut self, spec: &Spec, rule: Option<&Rule>) {
79        self.result.sampling_rate = rule.and_then(|r| r.sampling_rate);
80        self.result.forward_all_exposures = spec.forward_all_exposures;
81
82        if self.nested_count > 0 {
83            self.nested_count -= 1;
84            return;
85        }
86
87        if self.result.secondary_exposures.is_empty() {
88            return;
89        }
90
91        if self.result.undelegated_secondary_exposures.is_some() {
92            return;
93        }
94
95        self.result.undelegated_secondary_exposures = Some(self.result.secondary_exposures.clone());
96    }
97
98    pub fn prep_for_nested_evaluation(&mut self) -> Result<(), StatsigErr> {
99        self.nested_count += 1;
100
101        self.result.bool_value = false;
102        self.result.json_value = None;
103
104        if self.nested_count > MAX_RECURSIVE_DEPTH {
105            return Err(StackOverflowError);
106        }
107
108        Ok(())
109    }
110}