pub const REDIS_CLUSTER_SLOTS: u16 = 16_384;
pub fn redis_cluster_hash_tag(key: &str) -> &str {
let Some(start) = key.find('{') else {
return key;
};
let rest = &key[start + 1..];
let Some(end) = rest.find('}') else {
return key;
};
if end == 0 { key } else { &rest[..end] }
}
pub fn redis_cluster_slot(key: &str) -> u16 {
crc16(redis_cluster_hash_tag(key).as_bytes()) % REDIS_CLUSTER_SLOTS
}
fn crc16(bytes: &[u8]) -> u16 {
let mut crc = 0u16;
for byte in bytes {
crc ^= (*byte as u16) << 8;
for _ in 0..8 {
if crc & 0x8000 != 0 {
crc = (crc << 1) ^ 0x1021;
} else {
crc <<= 1;
}
}
}
crc
}
#[cfg(test)]
mod tests {
use super::{redis_cluster_hash_tag, redis_cluster_slot};
#[test]
fn slot_matches_redis_examples() {
assert_eq!(redis_cluster_slot("123456789"), 12739);
assert_eq!(redis_cluster_slot("{user1000}.following"), 3443);
assert_eq!(redis_cluster_slot("{user1000}.followers"), 3443);
}
#[test]
fn hash_tag_requires_non_empty_tag() {
assert_eq!(redis_cluster_hash_tag("foo{}{bar}"), "foo{}{bar}");
assert_eq!(redis_cluster_hash_tag("foo{bar}zap"), "bar");
}
}