statsig-rust 0.19.1-beta.2604110309

Statsig Rust SDK for usage in multi-user server environments.
Documentation
use crate::networking::ResponseData;
use crate::statsig_err::StatsigErr;
use crate::StatsigRuntime;
use async_trait::async_trait;
use serde::Deserialize;
use serde::Serialize;
use std::fmt::{self, Debug};
use std::sync::Arc;
use std::time::Duration;

#[repr(u8)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SpecsSource {
    Uninitialized = 0,
    NoValues,
    Error,
    Loading,
    Bootstrap,
    Adapter(String),
    Network,
}

const DEFAULT_CONFIG_COMPRESSION_MODE: ConfigCompressionMode = ConfigCompressionMode::Gzip;

#[derive(Clone)]
pub enum ConfigCompressionMode {
    Gzip,
    Dictionary,
}

impl From<&str> for ConfigCompressionMode {
    fn from(s: &str) -> Self {
        match s.to_lowercase().as_str() {
            "gzip" => ConfigCompressionMode::Gzip,
            "dictionary" => ConfigCompressionMode::Dictionary,
            _ => DEFAULT_CONFIG_COMPRESSION_MODE,
        }
    }
}

impl SpecsSource {
    pub fn new_from_string(s: &str) -> Self {
        if s.starts_with("Adapter(") {
            let name = s
                .strip_prefix("Adapter(")
                .and_then(|s| s.strip_suffix(")"))
                .unwrap_or("");
            return SpecsSource::Adapter(name.to_string());
        }

        match s {
            "Uninitialized" => SpecsSource::Uninitialized,
            "NoValues" => SpecsSource::NoValues,
            "Error" => SpecsSource::Error,
            "Loading" => SpecsSource::Loading,
            "Bootstrap" => SpecsSource::Bootstrap,
            "Network" => SpecsSource::Network,
            _ => SpecsSource::Error,
        }
    }
}

impl fmt::Display for SpecsSource {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s = match self {
            SpecsSource::Adapter(name) => {
                let type_name = format!("Adapter({name})");
                type_name
            }
            SpecsSource::Uninitialized => "Uninitialized".to_string(),
            SpecsSource::NoValues => "NoValues".to_string(),
            SpecsSource::Error => "Error".to_string(),
            SpecsSource::Loading => "Loading".to_string(),
            SpecsSource::Network => "Network".to_string(),
            SpecsSource::Bootstrap => "Bootstrap".to_string(),
        };
        write!(f, "{s}")
    }
}

#[async_trait]
pub trait SpecsAdapter: Send + Sync {
    fn initialize(&self, listener: Arc<dyn SpecsUpdateListener>);

    async fn start(
        self: Arc<Self>,
        statsig_runtime: &Arc<StatsigRuntime>,
    ) -> Result<(), StatsigErr>;

    async fn shutdown(
        &self,
        timeout: Duration,
        statsig_runtime: &Arc<StatsigRuntime>,
    ) -> Result<(), StatsigErr>;

    async fn schedule_background_sync(
        self: Arc<Self>,
        statsig_runtime: &Arc<StatsigRuntime>,
    ) -> Result<(), StatsigErr>;

    fn get_type_name(&self) -> String;
}

pub enum SpecsFormat {
    Json,
    Protobuf,
}

impl From<&SpecsFormat> for &str {
    fn from(val: &SpecsFormat) -> Self {
        match val {
            SpecsFormat::Json => "JSON",
            SpecsFormat::Protobuf => "Protobuf",
        }
    }
}

pub struct SpecsUpdate {
    pub data: ResponseData,
    pub source: SpecsSource,
    pub received_at: u64,
    pub source_api: Option<String>,
}

#[repr(C)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SpecsInfo {
    pub lcut: Option<u64>,
    pub checksum: Option<String>,
    pub source: SpecsSource,
    pub source_api: Option<String>,
}

impl SpecsInfo {
    #[must_use]
    pub fn empty() -> Self {
        Self {
            lcut: None,
            checksum: None,
            source: SpecsSource::NoValues,
            source_api: None,
        }
    }

    #[must_use]
    pub fn error() -> Self {
        Self {
            lcut: None,
            checksum: None,
            source: SpecsSource::Error,
            source_api: None,
        }
    }
}

pub trait SpecsUpdateListener: Send + Sync {
    fn did_receive_specs_update(&self, update: SpecsUpdate) -> Result<(), StatsigErr>;

    fn get_current_specs_info(&self) -> SpecsInfo;
}

impl fmt::Debug for dyn SpecsAdapter {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.get_type_name())
    }
}