statsig-rust 0.19.1-beta.2604130314

Statsig Rust SDK for usage in multi-user server environments.
Documentation
use crate::{
    evaluation::{dynamic_string::DynamicString, evaluator_value::MemoizedEvaluatorValue},
    unwrap_or_return, DynamicValue,
};

lazy_static::lazy_static! {
    static ref EMPTY_STRING: DynamicString = DynamicString::from(String::new());
}

pub(crate) fn compare_strings_in_array(
    value: &DynamicValue,
    target_value: &MemoizedEvaluatorValue,
    op: &str,
    ignore_case: bool,
) -> bool {
    let value_str = value.string_value.as_ref().unwrap_or(&EMPTY_STRING);

    let result = compare_strings_in_array_impl(value_str, target_value, op, ignore_case);

    if op == "none" || op == "none_case_sensitive" || op == "str_contains_none" {
        return !result;
    }
    result
}

fn compare_strings_in_array_impl(
    value_str: &DynamicString,
    target_value: &MemoizedEvaluatorValue,
    op: &str,
    ignore_case: bool,
) -> bool {
    if let Some(keyed_lookup) = &target_value.object_value {
        if ignore_case && (op == "any" || op == "none") {
            let contains = keyed_lookup.contains_key(&value_str.lowercased_value);
            return contains;
        }
    }

    let array_value = unwrap_or_return!(&target_value.array_value, false);
    if ignore_case && (op == "any" || op == "none") {
        let contains = array_value.contains_key(&value_str.lowercased_value);
        return contains;
    }

    let mut comparison_result = false;
    for (lowercase_str, (_, current_str)) in array_value {
        let left = if ignore_case {
            &value_str.lowercased_value
        } else {
            &value_str.value
        };

        let right = if ignore_case {
            lowercase_str
        } else {
            current_str
        };

        comparison_result = match op {
            "any" | "none" | "any_case_sensitive" | "none_case_sensitive" => left.eq(right),
            "str_starts_with_any" => left.starts_with(right.as_str()),
            "str_ends_with_any" => left.ends_with(right.as_str()),
            "str_contains_any" | "str_contains_none" => left.contains(right.as_str()),
            _ => false, // todo: unsupported?
        };

        if comparison_result {
            break;
        }
    }

    comparison_result
}

#[cfg(test)]
mod tests {
    use serde_json::json;

    use crate::evaluation::comparisons::compare_strings_in_array;
    use crate::{dyn_value, test_only_make_eval_value};

    #[test]
    fn test_array_contains() {
        let needle = dyn_value!("Foo");
        let haystack = test_only_make_eval_value!(["boo", "bar", "foo", "far", "zoo", "zar"]);

        assert!(compare_strings_in_array(&needle, &haystack, "any", true));
        assert!(!compare_strings_in_array(&needle, &haystack, "any", false));
    }

    #[test]
    fn test_array_does_not_contain() {
        let needle = dyn_value!("Foo");
        let haystack = test_only_make_eval_value!(vec!["boo", "bar", "far", "zoo", "zar"]);

        assert!(!compare_strings_in_array(&needle, &haystack, "any", true));

        assert!(!compare_strings_in_array(&needle, &haystack, "any", false));
    }

    #[test]
    fn test_str_starting_with() {
        let needle = dyn_value!("daniel@statsig.com");
        let haystack = test_only_make_eval_value!(vec!["tore", "daniel"]);

        assert!(compare_strings_in_array(
            &needle,
            &haystack,
            "str_starts_with_any",
            true
        ));
    }

    #[test]
    fn test_str_ending_with() {
        let needle = dyn_value!("tore@statsig.com");
        let haystack = test_only_make_eval_value!(vec!["@statsig.io", "@statsig.com"]);

        assert!(compare_strings_in_array(
            &needle,
            &haystack,
            "str_ends_with_any",
            true
        ));
    }

    #[test]
    fn test_str_contains_any() {
        let needle = dyn_value!("daniel@statsig.io");
        let haystack = test_only_make_eval_value!(vec!["sigstat", "statsig"]);

        assert!(compare_strings_in_array(
            &needle,
            &haystack,
            "str_contains_any",
            true
        ));
    }

    #[test]
    fn test_str_none_case_sensitive() {
        let haystack = test_only_make_eval_value!(vec!["HELLO", "WORLD"]);

        let upper_needle = dyn_value!("HELLO");
        assert!(!compare_strings_in_array(
            &upper_needle,
            &haystack,
            "none_case_sensitive",
            false
        ));

        let lower_needle = dyn_value!("hello");
        assert!(compare_strings_in_array(
            &lower_needle,
            &haystack,
            "none_case_sensitive",
            false
        ));
    }

    #[test]
    fn test_str_any_case_sensitive() {
        let haystack = test_only_make_eval_value!(vec!["HELLO", "WORLD"]);

        let upper_needle = dyn_value!("HELLO");
        assert!(compare_strings_in_array(
            &upper_needle,
            &haystack,
            "any_case_sensitive",
            false
        ));

        let lower_needle = dyn_value!("hello");
        assert!(!compare_strings_in_array(
            &lower_needle,
            &haystack,
            "any_case_sensitive",
            false
        ));
    }

    #[test]
    fn test_array_contains_any() {
        let needle = dyn_value!(json!(["boo", 1, true]));
        let haystack_positive = test_only_make_eval_value!(vec!["zoo", "boo"]);
        let haystack_negative = test_only_make_eval_value!(vec!["zoo", "bar"]);

        assert!(compare_strings_in_array(
            &needle,
            &haystack_positive,
            "str_contains_any",
            true
        ));

        assert!(!compare_strings_in_array(
            &needle,
            &haystack_negative,
            "str_contains_any",
            true
        ));
    }
}