sigstat 0.0.3-beta.2

Statsig Rust SDK for usage in multi-user server environments.
Documentation
use crate::evaluation::evaluation_types::SecondaryExposure;
use crate::event_logging::config_exposure::CONFIG_EXPOSURE_EVENT_NAME;
use crate::event_logging::gate_exposure::GATE_EXPOSURE_EVENT_NAME;
use crate::event_logging::layer_exposure::LAYER_EXPOSURE_EVENT_NAME;
use crate::event_logging::statsig_event::StatsigEvent;
use crate::sdk_diagnostics::diagnostics::DIAGNOSTICS_EVENT;
use crate::statsig_user_internal::{StatsigUserInternal, StatsigUserLoggable};
use chrono::Utc;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashSet;

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StatsigEventInternal {
    #[serde(flatten)]
    pub event_data: StatsigEvent,

    pub user: StatsigUserLoggable,
    pub time: u64,
    pub secondary_exposures: Option<Vec<SecondaryExposure>>,
}

impl StatsigEventInternal {
    pub fn new(
        user: StatsigUserInternal,
        event: StatsigEvent,
        secondary_exposures: Option<Vec<SecondaryExposure>>,
    ) -> Self {
        StatsigEventInternal {
            event_data: event,
            user: StatsigUserLoggable::new(user),
            time: Utc::now().timestamp_millis() as u64,
            secondary_exposures: secondary_exposure_keys_to_expos(secondary_exposures),
        }
    }

    pub fn new_diagnostic_event(event: StatsigEvent) -> Self {
        StatsigEventInternal {
            event_data: event,
            user: StatsigUserLoggable { value: Value::Null },
            time: Utc::now().timestamp_millis() as u64,
            secondary_exposures: None,
        }
    }

    pub fn new_non_exposed_checks_event(event: StatsigEvent) -> Self {
        StatsigEventInternal {
            event_data: event,
            user: StatsigUserLoggable { value: Value::Null },
            time: Utc::now().timestamp_millis() as u64,
            secondary_exposures: None,
        }
    }

    pub fn is_exposure_event(&self) -> bool {
        self.event_data.event_name == GATE_EXPOSURE_EVENT_NAME
            || self.event_data.event_name == CONFIG_EXPOSURE_EVENT_NAME
            || self.event_data.event_name == LAYER_EXPOSURE_EVENT_NAME
    }

    pub fn is_diagnostic_event(&self) -> bool {
        self.event_data.event_name == DIAGNOSTICS_EVENT
    }
}

pub(crate) fn make_custom_event(
    user: StatsigUserInternal,
    event: StatsigEvent,
) -> StatsigEventInternal {
    StatsigEventInternal::new(user, event, None)
}

fn secondary_exposure_keys_to_expos(
    secondary_exposures: Option<Vec<SecondaryExposure>>,
) -> Option<Vec<SecondaryExposure>> {
    match secondary_exposures.as_ref() {
        Some(secondary_exposures) => {
            let mut seen = HashSet::new();
            let mut filtered = Vec::new();
            for expo in secondary_exposures {
                let key = format!("{}.{}.{}", expo.gate, expo.rule_id, expo.gate_value);
                if !seen.contains(&key) {
                    seen.insert(key);
                    filtered.push(expo)
                }
            }

            Some(secondary_exposures.clone())
        }
        None => None,
    }
}

#[cfg(test)]
mod statsig_event_internal_tests {
    use crate::event_logging::statsig_event::StatsigEvent;
    use crate::event_logging::statsig_event_internal::{make_custom_event, StatsigEventInternal};
    use crate::statsig_user_internal::StatsigUserInternal;
    use crate::StatsigUser;
    use serde_json::json;
    use std::collections::HashMap;

    fn create_test_event() -> StatsigEventInternal {
        let user = StatsigUserInternal::new(&StatsigUser::with_user_id("a-user".into()), None);

        make_custom_event(
            user,
            StatsigEvent {
                event_name: "foo".into(),
                value: Some(json!("bar")),
                metadata: Some(HashMap::from([("key".into(), "value".into())])),
            },
        )
    }

    #[test]
    fn test_custom_event_fields() {
        let event = create_test_event();
        let data = event.event_data;

        assert_eq!(data.event_name, "foo");
        assert_eq!(data.value.unwrap().as_str(), Some("bar"));
        assert_eq!(data.metadata.unwrap().get("key").unwrap(), "value");
    }

    #[test]
    fn test_custom_event_serialization() {
        let event = create_test_event();

        let value = json!(event).as_object().cloned().unwrap();
        assert_eq!(value.get("eventName").unwrap(), "foo");
        assert_eq!(value.get("value").unwrap(), "bar");
        assert_eq!(
            value.get("metadata").unwrap().to_string(),
            "{\"key\":\"value\"}"
        );
    }
}