oai-statsig-rust 0.27.0

Statsig Rust SDK for usage in multi-user server environments.
Documentation
use ahash::AHashSet;
use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};

use crate::{
    evaluation::{
        dynamic_returnable::{DynamicReturnable, DynamicReturnableValue},
        dynamic_string::DynamicString,
    },
    specs_response::spec_types::{Rule, Spec},
};

use super::spec_content_hash::spec_content_hash;

#[derive(Archive, RkyvDeserialize, RkyvSerialize)]
#[rkyv(bytecheck(bounds(__C: rkyv::validation::ArchiveContext)))]
pub(crate) struct MmapSpec {
    pub(crate) content_hash: u64,
    pub(crate) checksum: Option<u64>,
    pub(crate) spec_type: u64,
    pub(crate) salt: u64,
    pub(crate) default_value: MmapReturnable,
    pub(crate) enabled: bool,
    pub(crate) rules: Vec<MmapRule>,
    pub(crate) id_type: u64,
    pub(crate) explicit_parameters: Option<Vec<u64>>,
    pub(crate) entity: u64,
    pub(crate) has_shared_params: Option<bool>,
    pub(crate) is_active: Option<bool>,
    pub(crate) version: Option<u32>,
    pub(crate) target_app_ids: Option<Vec<u64>>,
    pub(crate) forward_all_exposures: Option<bool>,
    pub(crate) fields_used: Option<Vec<u64>>,
    pub(crate) use_new_layer_eval: Option<bool>,
}

impl MmapSpec {
    pub(crate) fn from_owned(spec: &Spec) -> Self {
        Self {
            content_hash: spec_content_hash(spec),
            checksum: spec.checksum.as_ref().map(|value| value.hash),
            spec_type: spec._type.hash,
            salt: spec.salt.hash,
            default_value: MmapReturnable::from_owned(&spec.default_value),
            enabled: spec.enabled,
            rules: spec.rules.iter().map(MmapRule::from_owned).collect(),
            id_type: spec.id_type.hash,
            explicit_parameters: spec.explicit_parameters.as_ref().map(|parameters| {
                parameters
                    .as_slice()
                    .iter()
                    .map(|value| value.hash)
                    .collect()
            }),
            entity: spec.entity.hash,
            has_shared_params: spec.has_shared_params,
            is_active: spec.is_active,
            version: spec.version,
            target_app_ids: spec
                .target_app_ids
                .as_ref()
                .map(|values| values.iter().map(|value| value.hash).collect()),
            forward_all_exposures: spec.forward_all_exposures,
            fields_used: spec
                .fields_used
                .as_ref()
                .map(|values| values.iter().map(|value| value.hash).collect()),
            use_new_layer_eval: spec.use_new_layer_eval,
        }
    }

    pub(crate) fn collect_references(
        &self,
        string_hashes: &mut AHashSet<u64>,
        returnable_hashes: &mut AHashSet<u64>,
    ) {
        string_hashes.extend(self.checksum);
        string_hashes.insert(self.spec_type);
        string_hashes.insert(self.salt);
        self.default_value.collect_hash(returnable_hashes);
        string_hashes.insert(self.id_type);
        if let Some(values) = &self.explicit_parameters {
            string_hashes.extend(values);
        }
        string_hashes.insert(self.entity);
        if let Some(values) = &self.target_app_ids {
            string_hashes.extend(values);
        }
        if let Some(values) = &self.fields_used {
            string_hashes.extend(values);
        }

        for rule in &self.rules {
            string_hashes.insert(rule.name);
            rule.return_value.collect_hash(returnable_hashes);
            string_hashes.insert(rule.id);
            string_hashes.extend(rule.salt);
            string_hashes.extend(&rule.conditions);
            string_hashes.insert(rule.id_type.value);
            string_hashes.insert(rule.id_type.lowercased_value);
            string_hashes.extend(rule.group_name);
            string_hashes.extend(rule.config_delegate);
        }
    }
}

#[derive(Archive, RkyvDeserialize, RkyvSerialize)]
#[rkyv(bytecheck(bounds(__C: rkyv::validation::ArchiveContext)))]
pub(crate) struct MmapRule {
    pub(crate) name: u64,
    pub(crate) pass_percentage: f64,
    pub(crate) return_value: MmapReturnable,
    pub(crate) id: u64,
    pub(crate) salt: Option<u64>,
    pub(crate) conditions: Vec<u64>,
    pub(crate) id_type: MmapDynamicString,
    pub(crate) group_name: Option<u64>,
    pub(crate) config_delegate: Option<u64>,
    pub(crate) is_experiment_group: Option<bool>,
    pub(crate) sampling_rate: Option<u64>,
}

impl MmapRule {
    fn from_owned(rule: &Rule) -> Self {
        Self {
            name: rule.name.hash,
            pass_percentage: rule.pass_percentage,
            return_value: MmapReturnable::from_owned(&rule.return_value),
            id: rule.id.hash,
            salt: rule.salt.as_ref().map(|value| value.hash),
            conditions: rule.conditions.iter().map(|value| value.hash).collect(),
            id_type: MmapDynamicString::from_owned(&rule.id_type),
            group_name: rule.group_name.as_ref().map(|value| value.hash),
            config_delegate: rule.config_delegate.as_ref().map(|value| value.hash),
            is_experiment_group: rule.is_experiment_group,
            sampling_rate: rule.sampling_rate,
        }
    }
}

#[derive(Archive, RkyvDeserialize, RkyvSerialize)]
#[rkyv(bytecheck(bounds(__C: rkyv::validation::ArchiveContext)))]
pub(crate) struct MmapDynamicString {
    pub(crate) value: u64,
    pub(crate) lowercased_value: u64,
}

impl MmapDynamicString {
    fn from_owned(value: &DynamicString) -> Self {
        Self {
            value: value.value.hash,
            lowercased_value: value.lowercased_value.hash,
        }
    }
}

#[derive(Archive, RkyvDeserialize, RkyvSerialize)]
#[rkyv(bytecheck(bounds(__C: rkyv::validation::ArchiveContext)))]
pub(crate) enum MmapReturnable {
    Null,
    Bool(bool),
    Json(u64),
}

impl MmapReturnable {
    fn from_owned(value: &DynamicReturnable) -> Self {
        match &value.value {
            DynamicReturnableValue::Null => Self::Null,
            DynamicReturnableValue::Bool(value) => Self::Bool(*value),
            DynamicReturnableValue::JsonPointer(_)
            | DynamicReturnableValue::JsonStatic(_)
            | DynamicReturnableValue::JsonArchived(_) => Self::Json(value.hash),
        }
    }

    fn collect_hash(&self, hashes: &mut AHashSet<u64>) {
        if let Self::Json(hash) = self {
            hashes.insert(*hash);
        }
    }
}