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,
};

pub(crate) fn compare_versions<'a>(
    left: UserValueRef<'_>,
    right: impl Into<EvaluatorValueRef<'a>>,
    op: ConditionOperator,
) -> bool {
    let right = right.into();
    let left_str = unwrap_or_return!(left.string_value(), false);
    let right_str = unwrap_or_return!(right.string_value(), false);

    let result = match compare_versions_impl(left_str, right_str) {
        ComparisonResult::Ok(result) => result,
        ComparisonResult::NumericParseFailure => return false,
    };

    match op {
        ConditionOperator::VersionGt => result > 0,
        ConditionOperator::VersionGte => result >= 0,
        ConditionOperator::VersionLt => result < 0,
        ConditionOperator::VersionLte => result <= 0,
        ConditionOperator::VersionEq => result == 0,
        ConditionOperator::VersionNeq => result != 0,
        _ => false,
    }
}

enum ComparisonResult {
    NumericParseFailure,
    Ok(i32),
}

fn compare_versions_impl(left_str: &str, right_str: &str) -> ComparisonResult {
    let left_version = left_str.split('-').next().unwrap_or("");
    let right_version = right_str.split('-').next().unwrap_or("");

    let mut left_parts = left_version.split('.');
    let mut right_parts = right_version.split('.');

    loop {
        let left_num = match next_num(left_parts.next()) {
            Ok(v) => v,
            Err(_) => return ComparisonResult::NumericParseFailure,
        };

        let right_num = match next_num(right_parts.next()) {
            Ok(v) => v,
            Err(_) => return ComparisonResult::NumericParseFailure,
        };

        if left_num.is_none() && right_num.is_none() {
            break;
        }

        let left_num = left_num.unwrap_or(0);
        let right_num = right_num.unwrap_or(0);

        if left_num < right_num {
            return ComparisonResult::Ok(-1);
        }

        if left_num > right_num {
            return ComparisonResult::Ok(1);
        }
    }

    ComparisonResult::Ok(0)
}

fn next_num(part: Option<&str>) -> Result<Option<i128>, std::num::ParseIntError> {
    part.map(|s| s.trim().parse::<i128>()).transpose()
}

#[cfg(test)]
mod tests {
    use crate::evaluation::comparisons::compare_versions;
    use crate::specs_response::spec_types::ConditionOperator;
    use crate::{dyn_value, test_only_make_eval_value};

    #[test]
    fn test_version_comparison_equal() {
        let left = dyn_value!("1.2.3");
        let right = test_only_make_eval_value!("1.2.3");

        let result = compare_versions((&left).into(), &right, ConditionOperator::VersionEq);
        assert!(result);
    }

    #[test]
    fn test_version_comparison_greater_than() {
        let left = dyn_value!("1.2.4");
        let right = test_only_make_eval_value!("1.2.3");

        let result = compare_versions((&left).into(), &right, ConditionOperator::VersionGt);
        assert!(result);
    }

    #[test]
    fn test_version_comparison_less_than() {
        let left = dyn_value!("1.2.3");
        let right = test_only_make_eval_value!("1.2.4");

        let result = compare_versions((&left).into(), &right, ConditionOperator::VersionLt);
        assert!(result);
    }
}