statsig-rust 0.19.1-beta.2604130314

Statsig Rust SDK for usage in multi-user server environments.
Documentation
use std::collections::HashMap;
use std::sync::Arc;

use crate::evaluation::dynamic_value::DynamicValue;
use crate::evaluation::evaluator_result::EvaluatorResult;
use crate::hashing::HashUtil;
use crate::id_lists_adapter::IdList;
use crate::interned_string::InternedString;
use crate::specs_response::spec_types::{Rule, Spec, SpecsResponseFull};
use crate::user::StatsigUserInternal;
use crate::StatsigErr::StackOverflowError;
use crate::{OverrideAdapter, SecondaryExposure, Statsig, StatsigErr};

const MAX_RECURSIVE_DEPTH: u16 = 300;

// (gate_name, (bool_value, rule_id, secondary_exposures))
type NestedGateMemo =
    HashMap<InternedString, (bool, Option<InternedString>, Vec<SecondaryExposure>)>;

pub enum IdListResolution<'a> {
    MapLookup(&'a HashMap<String, IdList>),
    Callback(&'a dyn Fn(&str, &str) -> bool),
}

pub struct EvaluatorContext<'a> {
    pub user: &'a StatsigUserInternal<'a, 'a>,
    pub specs_data: &'a SpecsResponseFull,
    pub id_list_resolver: IdListResolution<'a>,
    pub hashing: &'a HashUtil,
    pub result: EvaluatorResult,
    pub nested_count: u16,
    pub app_id: Option<&'a DynamicValue>,
    pub override_adapter: Option<&'a Arc<dyn OverrideAdapter>>,
    pub nested_gate_memo: NestedGateMemo,
    pub should_user_third_party_parser: bool,
    pub statsig: Option<&'a Statsig>,
    pub disable_exposure_logging: bool,
    pub gcir_hashes: Vec<u64>,
}

impl<'a> EvaluatorContext<'a> {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        user: &'a StatsigUserInternal,
        specs_data: &'a SpecsResponseFull,
        id_list_resolver: IdListResolution<'a>,
        hashing: &'a HashUtil,
        app_id: Option<&'a DynamicValue>,
        override_adapter: Option<&'a Arc<dyn OverrideAdapter>>,
        should_user_third_party_parser: bool,
        statsig: Option<&'a Statsig>,
        disable_exposure_logging: bool,
    ) -> Self {
        let result = EvaluatorResult::default();

        Self {
            user,
            specs_data,
            id_list_resolver,
            hashing,
            app_id,
            result,
            override_adapter,
            nested_count: 0,
            nested_gate_memo: HashMap::new(),
            should_user_third_party_parser,
            statsig,
            disable_exposure_logging,
            gcir_hashes: Vec::new(),
        }
    }

    pub fn reset_result(&mut self) {
        self.nested_count = 0;
        self.result = EvaluatorResult::default();
    }

    pub fn finalize_evaluation(&mut self, spec: &Spec, rule: Option<&Rule>) {
        self.result.sampling_rate = rule.and_then(|r| r.sampling_rate);
        self.result.forward_all_exposures = spec.forward_all_exposures;

        if self.nested_count > 0 {
            self.nested_count -= 1;
            return;
        }

        if self.result.secondary_exposures.is_empty() {
            return;
        }

        if self.result.undelegated_secondary_exposures.is_some() {
            return;
        }

        self.result.undelegated_secondary_exposures = Some(self.result.secondary_exposures.clone());
    }

    pub fn prep_for_nested_evaluation(&mut self) -> Result<(), StatsigErr> {
        self.nested_count += 1;

        self.result.bool_value = false;
        self.result.json_value = None;

        if self.nested_count > MAX_RECURSIVE_DEPTH {
            return Err(StackOverflowError);
        }

        Ok(())
    }
}