absmartly-sdk 0.1.0

ABsmartly SDK for Rust - A/B testing and feature flagging
Documentation
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};

use crate::md5::md5;

pub fn base64_url_no_padding(data: &[u8]) -> String {
    URL_SAFE_NO_PAD.encode(data)
}

pub fn hash_unit(value: &str) -> String {
    let hash = md5(value.as_bytes());
    base64_url_no_padding(&hash)
}

pub fn choose_variant(split: &[f64], prob: f64) -> usize {
    let mut cum_sum = 0.0;
    for (i, &weight) in split.iter().enumerate() {
        cum_sum += weight;
        if prob < cum_sum {
            return i;
        }
    }
    split.len().saturating_sub(1)
}

pub fn array_equals_shallow<T: PartialEq>(a: &[T], b: &[T]) -> bool {
    a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| x == y)
}

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

    #[test]
    fn test_base64_url_no_padding() {
        assert_eq!(base64_url_no_padding(b""), "");
        assert_eq!(base64_url_no_padding(b" "), "IA");
        assert_eq!(base64_url_no_padding(b"t"), "dA");
        assert_eq!(base64_url_no_padding(b"te"), "dGU");
        assert_eq!(base64_url_no_padding(b"tes"), "dGVz");
        assert_eq!(base64_url_no_padding(b"test"), "dGVzdA");
        assert_eq!(base64_url_no_padding(b"testy"), "dGVzdHk");
        assert_eq!(base64_url_no_padding(b"testy1"), "dGVzdHkx");
        assert_eq!(base64_url_no_padding(b"testy12"), "dGVzdHkxMg");
        assert_eq!(base64_url_no_padding(b"testy123"), "dGVzdHkxMjM");
        assert_eq!(
            base64_url_no_padding(b"The quick brown fox jumps over the lazy dog"),
            "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw"
        );
    }

    #[test]
    fn test_base64_url_no_padding_special_chars() {
        let special = "special characters açb↓c".as_bytes();
        assert_eq!(base64_url_no_padding(special), "c3BlY2lhbCBjaGFyYWN0ZXJzIGHDp2LihpNj");
    }

    #[test]
    fn test_hash_unit_known_values() {
        assert_eq!(
            hash_unit("4a42766ca6313d26f49985e799ff4f3790fb86efa0fce46edb3ea8fbf1ea3408"),
            "H2jvj6o9YcAgNdhKqEbtWw"
        );
        assert_eq!(hash_unit("bleh@absmarty.com"), "DRgslOje35bZMmpaohQjkA");
        assert_eq!(hash_unit("açb↓c"), "LxcqH5VC15rXfWfA_smreg");
        assert_eq!(hash_unit("testy"), "K5I_V6RgP8c6sYKz-TVn8g");
    }

    #[test]
    fn test_hash_unit_no_padding_or_special_chars() {
        let hash = hash_unit("test123");
        assert!(!hash.is_empty());
        assert!(!hash.contains('+'));
        assert!(!hash.contains('/'));
        assert!(!hash.contains('='));
    }

    #[test]
    fn test_choose_variant_two_way_split_zero_first() {
        assert_eq!(choose_variant(&[0.0, 1.0], 0.0), 1);
        assert_eq!(choose_variant(&[0.0, 1.0], 0.5), 1);
        assert_eq!(choose_variant(&[0.0, 1.0], 1.0), 1);
    }

    #[test]
    fn test_choose_variant_two_way_split_full_first() {
        assert_eq!(choose_variant(&[1.0, 0.0], 0.0), 0);
        assert_eq!(choose_variant(&[1.0, 0.0], 0.5), 0);
        assert_eq!(choose_variant(&[1.0, 0.0], 1.0), 1);
    }

    #[test]
    fn test_choose_variant_two_way_split_even() {
        assert_eq!(choose_variant(&[0.5, 0.5], 0.0), 0);
        assert_eq!(choose_variant(&[0.5, 0.5], 0.25), 0);
        assert_eq!(choose_variant(&[0.5, 0.5], 0.49999999), 0);
        assert_eq!(choose_variant(&[0.5, 0.5], 0.5), 1);
        assert_eq!(choose_variant(&[0.5, 0.5], 0.50000001), 1);
        assert_eq!(choose_variant(&[0.5, 0.5], 0.75), 1);
        assert_eq!(choose_variant(&[0.5, 0.5], 1.0), 1);
    }

    #[test]
    fn test_choose_variant_three_way_split() {
        assert_eq!(choose_variant(&[0.333, 0.333, 0.334], 0.0), 0);
        assert_eq!(choose_variant(&[0.333, 0.333, 0.334], 0.25), 0);
        assert_eq!(choose_variant(&[0.333, 0.333, 0.334], 0.33299999), 0);
        assert_eq!(choose_variant(&[0.333, 0.333, 0.334], 0.333), 1);
        assert_eq!(choose_variant(&[0.333, 0.333, 0.334], 0.33300001), 1);
        assert_eq!(choose_variant(&[0.333, 0.333, 0.334], 0.5), 1);
        assert_eq!(choose_variant(&[0.333, 0.333, 0.334], 0.66599999), 1);
        assert_eq!(choose_variant(&[0.333, 0.333, 0.334], 0.666), 2);
        assert_eq!(choose_variant(&[0.333, 0.333, 0.334], 0.66600001), 2);
        assert_eq!(choose_variant(&[0.333, 0.333, 0.334], 0.75), 2);
        assert_eq!(choose_variant(&[0.333, 0.333, 0.334], 1.0), 2);
    }

    #[test]
    fn test_array_equals_shallow() {
        assert!(array_equals_shallow::<i32>(&[], &[]));
        assert!(array_equals_shallow(&[1], &[1]));
        assert!(array_equals_shallow(&[1, 2, 3], &[1, 2, 3]));
        assert!(array_equals_shallow(&[0.5, 1.0, 1.5], &[0.5, 1.0, 1.5]));

        assert!(!array_equals_shallow(&[1], &[]));
        assert!(!array_equals_shallow(&[], &[1]));
        assert!(!array_equals_shallow(&[1, 2, 3], &[1, 2]));
        assert!(!array_equals_shallow(&[1, 2], &[1, 2, 3]));
    }
}