statsig-rust 0.19.1-beta.2604130314

Statsig Rust SDK for usage in multi-user server environments.
Documentation
use chrono::Utc;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::evaluation::evaluation_details::EvaluationDetails;

pub const TRUE_STR: &str = "true";
pub const FALSE_STR: &str = "false";

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum KeyType {
    #[serde(rename = "initialize")]
    Initialize,
    #[serde(rename = "overall")]
    Overall,
    #[serde(rename = "download_config_specs")]
    DownloadConfigSpecs,
    #[serde(rename = "get_id_list")]
    GetIDList,
    #[serde(rename = "get_id_list_sources")]
    GetIDListSources,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum StepType {
    #[serde(rename = "process")]
    Process,
    #[serde(rename = "network_request")]
    NetworkRequest,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum ActionType {
    #[serde(rename = "start")]
    Start,
    #[serde(rename = "end")]
    End,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Marker {
    pub key: KeyType,

    action: ActionType,

    timestamp: u64,

    #[serde(skip_serializing_if = "Option::is_none")]
    attempt: Option<u32>,

    #[serde(skip_serializing_if = "Option::is_none")]
    config_name: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    error: Option<HashMap<String, String>>,

    #[serde(skip_serializing_if = "Option::is_none")]
    id_list_count: Option<u32>,

    #[serde(rename = "markerID")]
    #[serde(skip_serializing_if = "Option::is_none")]
    marker_id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    message: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    sdk_region: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    status_code: Option<u16>,

    #[serde(skip_serializing_if = "Option::is_none")]
    step: Option<StepType>,

    #[serde(skip_serializing_if = "Option::is_none")]
    success: Option<bool>,

    #[serde(skip_serializing_if = "Option::is_none")]
    url: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    evaluation_details: Option<EvaluationDetails>,

    #[serde(skip_serializing_if = "Option::is_none")]
    config_spec_ready: Option<bool>,

    #[serde(skip_serializing_if = "Option::is_none")]
    source: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    content_type: Option<String>,
}

impl Marker {
    #[must_use]
    pub fn new(key: KeyType, action: ActionType, step: Option<StepType>) -> Self {
        Self {
            key,
            action,
            step,
            success: None,
            timestamp: Utc::now().timestamp_millis() as u64,
            status_code: None,
            url: None,
            id_list_count: None,
            sdk_region: None,
            marker_id: None,
            attempt: None,
            config_name: None,
            message: None,
            error: None,
            evaluation_details: None,
            config_spec_ready: None,
            source: None,
            content_type: None,
        }
    }

    #[must_use]
    pub fn with_is_success(mut self, success: bool) -> Self {
        self.success = Some(success);
        self
    }

    #[must_use]
    pub fn with_status_code(mut self, status_code: u16) -> Self {
        self.status_code = Some(status_code);
        self
    }

    #[must_use]
    pub fn with_attempt(mut self, attempt: u32) -> Self {
        self.attempt = Some(attempt);
        self
    }

    #[must_use]
    pub fn with_url(mut self, url: String) -> Self {
        self.url = Some(url);
        self
    }

    #[must_use]
    pub fn with_message(mut self, message: String) -> Self {
        self.message = Some(message);
        self
    }

    #[must_use]
    pub fn with_eval_details(mut self, details: EvaluationDetails) -> Self {
        self.evaluation_details = Some(details);
        self
    }

    #[must_use]
    pub fn with_config_spec_ready(mut self, ready: bool) -> Self {
        self.config_spec_ready = Some(ready);
        self
    }

    #[must_use]
    pub fn with_source(mut self, source: String) -> Self {
        self.source = Some(source);
        self
    }

    #[must_use]
    pub fn with_sdk_region(mut self, region: Option<String>) -> Self {
        self.sdk_region = region;
        self
    }

    #[must_use]
    pub fn with_id_list_count(mut self, count: usize) -> Self {
        if count <= u32::MAX as usize {
            self.id_list_count = Some(count as u32);
        }
        self
    }

    #[must_use]
    pub fn with_error(mut self, error: HashMap<String, String>) -> Self {
        self.error = Some(error);
        self
    }

    #[must_use]
    pub fn with_content_type(mut self, content_type: Option<String>) -> Self {
        self.content_type = content_type;
        self
    }
    // TODO add more as needed
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_marker_new() {
        let timestamp: u64 = Utc::now().timestamp_millis() as u64;
        let marker = Marker::new(
            KeyType::Initialize,
            ActionType::Start,
            Some(StepType::Process),
        );

        assert_eq!(marker.key, KeyType::Initialize);
        assert_eq!(marker.action, ActionType::Start);
        assert_eq!(marker.step, Some(StepType::Process));
        assert_eq!(marker.success, None);
        assert_eq!(marker.status_code, None);
        assert_eq!(marker.timestamp, timestamp);
    }

    #[test]
    fn test_marker_serialization() {
        let timestamp: u64 = Utc::now().timestamp_millis() as u64;
        let marker = Marker::new(
            KeyType::Initialize,
            ActionType::Start,
            Some(StepType::NetworkRequest),
        )
        .with_is_success(true)
        .with_status_code(200)
        .with_attempt(1);

        let serialized = serde_json::to_string(&marker).expect("Failed to serialize Marker");
        let expected_json = format!(
            r#"{{"key":"initialize","action":"start","timestamp":{timestamp},"attempt":1,"statusCode":200,"step":"network_request","success":true}}"#
        );
        assert_eq!(serialized, expected_json);
    }
}