oai-statsig-rust 0.27.0

Statsig Rust SDK for usage in multi-user server environments.
Documentation
use crate::{
    evaluation::evaluator_value::EvaluatorValueRef, specs_response::spec_types::ConditionOperator,
    unwrap_or_return, user::user_value::UserValueRef,
};
use ahash::AHashSet;

pub(crate) fn compare_arrays<'a>(
    value: UserValueRef<'_>,
    target_value: impl Into<EvaluatorValueRef<'a>>,
    op: ConditionOperator,
) -> bool {
    let target_value = target_value.into();
    unwrap_or_return!(target_value.array_len(), false);
    let value_len = unwrap_or_return!(value.array_len(), false);
    let value_set: AHashSet<&str> = AHashSet::from_iter((0..value_len).map(|index| {
        value
            .array_item(index)
            .and_then(UserValueRef::string_value)
            .unwrap_or_default()
    }));

    let short_circuit = target_value.any_array_entry(|_, _, item| match op {
        ConditionOperator::ArrayContainsAll => !value_set.contains(item),
        ConditionOperator::ArrayContainsAny => value_set.contains(item),
        ConditionOperator::ArrayContainsNone => value_set.contains(item),
        ConditionOperator::NotArrayContainsAll => !value_set.contains(item),
        _ => true,
    });

    match op {
        ConditionOperator::ArrayContainsAll => !short_circuit,
        ConditionOperator::ArrayContainsAny => short_circuit,
        ConditionOperator::ArrayContainsNone => !short_circuit,
        ConditionOperator::NotArrayContainsAll => short_circuit,
        _ => false,
    }
}

#[cfg(test)]
mod tests {
    use super::compare_arrays;
    use crate::{
        dyn_value,
        evaluation::evaluator_value::{EvaluatorValueType, MemoizedEvaluatorValue},
        specs_response::spec_types::ConditionOperator,
        test_only_make_eval_value,
    };
    use serde_json::json;

    fn compare(
        value: serde_json::Value,
        target: &MemoizedEvaluatorValue,
        op: ConditionOperator,
    ) -> bool {
        let value = dyn_value!(value);
        compare_arrays((&value).into(), target, op)
    }

    #[test]
    fn array_operators_preserve_quantifier_semantics() {
        let target = test_only_make_eval_value!(["a", "b"]);

        let cases = [
            (json!(["a", "b", "c"]), true, true, false, false),
            (json!(["a"]), false, true, false, true),
            (json!(["z"]), false, false, true, true),
            (json!([]), false, false, true, true),
        ];

        for (value, all, any, none, not_all) in cases {
            assert_eq!(
                compare(value.clone(), &target, ConditionOperator::ArrayContainsAll),
                all
            );
            assert_eq!(
                compare(value.clone(), &target, ConditionOperator::ArrayContainsAny),
                any
            );
            assert_eq!(
                compare(value.clone(), &target, ConditionOperator::ArrayContainsNone),
                none
            );
            assert_eq!(
                compare(value, &target, ConditionOperator::NotArrayContainsAll),
                not_all
            );
        }
    }

    #[test]
    fn empty_and_missing_targets_keep_existing_behavior() {
        let empty = MemoizedEvaluatorValue::from(json!([]));
        let value = json!(["a"]);

        assert!(compare(
            value.clone(),
            &empty,
            ConditionOperator::ArrayContainsAll
        ));
        assert!(!compare(
            value.clone(),
            &empty,
            ConditionOperator::ArrayContainsAny
        ));
        assert!(compare(
            value.clone(),
            &empty,
            ConditionOperator::ArrayContainsNone
        ));
        assert!(!compare(
            value.clone(),
            &empty,
            ConditionOperator::NotArrayContainsAll
        ));

        let missing = MemoizedEvaluatorValue::new(EvaluatorValueType::Array);
        for op in [
            ConditionOperator::ArrayContainsAll,
            ConditionOperator::ArrayContainsAny,
            ConditionOperator::ArrayContainsNone,
            ConditionOperator::NotArrayContainsAll,
        ] {
            assert!(!compare(value.clone(), &missing, op));
        }
    }
}