sigstat 0.0.4

Statsig Rust SDK for usage in multi-user server environments.
Documentation
use regex_lite::Regex;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::{json, Value as JsonValue};
use std::collections::HashMap;

#[macro_export]
macro_rules! dyn_value {
    ($x:expr) => {
        DynamicValue::from($x)
    };
}

#[derive(Debug, Clone, Default)]
pub struct DynamicValue {
    pub null: Option<()>,
    pub bool_value: Option<bool>,
    pub int_value: Option<i64>,
    pub float_value: Option<f64>,
    pub string_value: Option<String>,
    pub lowercase_string_value: Option<String>,
    pub array_value: Option<Vec<DynamicValue>>,
    pub object_value: Option<HashMap<String, DynamicValue>>,
    pub regex_value: Option<Regex>,
    pub json_value: JsonValue,
}

impl From<String> for DynamicValue {
    fn from(value: String) -> Self {
        let json_value = json!(value);
        let float_value = value.parse().ok();
        let int_value = value.parse().ok();
        DynamicValue {
            float_value,
            int_value,
            json_value,
            lowercase_string_value: Some(value.to_lowercase()),
            string_value: Some(value),
            ..Self::default()
        }
    }
}

impl From<&str> for DynamicValue {
    fn from(value: &str) -> Self {
        DynamicValue::from(value.to_string())
    }
}

impl From<usize> for DynamicValue {
    fn from(value: usize) -> Self {
        DynamicValue {
            json_value: json!(value),
            int_value: Some(value as i64),
            float_value: Some(value as f64),
            string_value: Some(value.to_string()),
            lowercase_string_value: Some(value.to_string()),
            ..Self::default()
        }
    }
}

impl From<i64> for DynamicValue {
    fn from(value: i64) -> Self {
        DynamicValue {
            int_value: Some(value),
            float_value: Some(value as f64),
            string_value: Some(value.to_string()),
            lowercase_string_value: Some(value.to_string()),
            ..Self::default()
        }
    }
}

impl From<i32> for DynamicValue {
    fn from(value: i32) -> Self {
        Self::from(value as i64)
    }
}

impl From<JsonValue> for DynamicValue {
    fn from(value: JsonValue) -> Self {
        let json_value = value.clone();
        match value {
            JsonValue::Null => DynamicValue {
                null: Some(()),
                json_value,
                ..DynamicValue::new()
            },
            JsonValue::Bool(b) => DynamicValue {
                bool_value: Some(b),
                string_value: Some(b.to_string()),
                json_value,
                lowercase_string_value: Some(b.to_string().to_lowercase()),
                ..DynamicValue::new()
            },
            JsonValue::Number(n) => DynamicValue {
                float_value: n.as_f64(),
                int_value: n.as_i64(),
                string_value: Some(json_value.to_string()),
                lowercase_string_value: Some(json_value.to_string()),
                json_value,
                ..DynamicValue::new()
            },
            JsonValue::String(s) => DynamicValue::from(s),
            JsonValue::Array(arr) => DynamicValue {
                array_value: Some(arr.into_iter().map(DynamicValue::from).collect()),
                string_value: Some(json_value.to_string()),
                lowercase_string_value: Some(json_value.to_string().to_lowercase()),
                json_value,
                ..DynamicValue::new()
            },
            JsonValue::Object(obj) => DynamicValue {
                object_value: Some(
                    obj.into_iter()
                        .map(|(k, v)| (k, DynamicValue::from(v)))
                        .collect(),
                ),
                json_value,
                ..DynamicValue::new()
            },
        }
    }
}

impl DynamicValue {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn from<T: Into<DynamicValue>>(value: T) -> Self {
        value.into()
    }

    pub fn for_timestamp_evaluation(timestamp: i64) -> DynamicValue {
        DynamicValue {
            int_value: Some(timestamp),
            ..DynamicValue::default()
        }
    }

    pub fn compile_regex(&mut self) {
        if let Some(value) = &self.string_value {
            if let Ok(regex) = Regex::new(value) {
                self.regex_value = Some(regex);
            }
        }
    }
}

impl PartialEq for DynamicValue {
    fn eq(&self, other: &Self) -> bool {
        self.null == other.null
            && self.bool_value == other.bool_value
            && self.int_value == other.int_value
            && self.float_value == other.float_value
            && self.string_value == other.string_value
            && self.array_value == other.array_value
            && self.object_value == other.object_value
    }
}

impl Serialize for DynamicValue {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        self.json_value.serialize(serializer)
    }
}

impl<'de> Deserialize<'de> for DynamicValue {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let json_value = JsonValue::deserialize(deserializer)?;
        Ok(DynamicValue::from(json_value))
    }
}