use crate::proto::pb::{CHASH_KEYFUN_BUCKETONLY, CHASH_KEYFUN_CUSTOM, CHASH_KEYFUN_STD};
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub enum KeyFun {
#[default]
Std,
BucketOnly,
}
#[derive(Debug, thiserror::Error, Eq, PartialEq)]
#[non_exhaustive]
pub enum KeyFunError {
#[error("chash_keyfun: CUSTOM (user-defined) is reserved but not implemented")]
Custom,
#[error("chash_keyfun: unknown selector {0}")]
Unknown(u32),
}
impl KeyFun {
#[must_use]
pub fn route_bytes(self, bucket: &[u8], key: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(self.route_len(bucket, key));
self.route_bytes_into(bucket, key, &mut out);
out
}
pub fn route_bytes_into(self, bucket: &[u8], key: &[u8], buf: &mut Vec<u8>) {
match self {
Self::Std => {
buf.extend_from_slice(bucket);
buf.push(b'/');
buf.extend_from_slice(key);
}
Self::BucketOnly => {
buf.extend_from_slice(bucket);
}
}
}
#[must_use]
pub fn route_len(self, bucket: &[u8], key: &[u8]) -> usize {
match self {
Self::Std => bucket.len() + 1 + key.len(),
Self::BucketOnly => bucket.len(),
}
}
pub fn from_wire(value: u32) -> Result<Self, KeyFunError> {
match value {
CHASH_KEYFUN_STD => Ok(Self::Std),
CHASH_KEYFUN_BUCKETONLY => Ok(Self::BucketOnly),
CHASH_KEYFUN_CUSTOM => Err(KeyFunError::Custom),
other => Err(KeyFunError::Unknown(other)),
}
}
#[must_use]
pub fn to_wire(self) -> u32 {
match self {
Self::Std => CHASH_KEYFUN_STD,
Self::BucketOnly => CHASH_KEYFUN_BUCKETONLY,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn std_shapes_bucket_slash_key() {
let bytes = KeyFun::Std.route_bytes(b"users", b"alice");
assert_eq!(bytes, b"users/alice");
}
#[test]
fn bucket_only_drops_key() {
let a = KeyFun::BucketOnly.route_bytes(b"users", b"alice");
let b = KeyFun::BucketOnly.route_bytes(b"users", b"bob");
assert_eq!(a, b);
assert_eq!(a, b"users");
}
#[test]
fn route_bytes_into_matches_route_bytes() {
for kf in [KeyFun::Std, KeyFun::BucketOnly] {
let owned = kf.route_bytes(b"b", b"k");
let mut buf = Vec::new();
kf.route_bytes_into(b"b", b"k", &mut buf);
assert_eq!(owned, buf, "kf = {kf:?}");
assert_eq!(owned.len(), kf.route_len(b"b", b"k"));
}
}
#[test]
fn from_wire_round_trips() {
for kf in [KeyFun::Std, KeyFun::BucketOnly] {
let w = kf.to_wire();
let back = KeyFun::from_wire(w).expect("known");
assert_eq!(back, kf);
}
}
#[test]
fn from_wire_rejects_custom_and_unknown() {
assert_eq!(KeyFun::from_wire(99), Err(KeyFunError::Custom));
assert_eq!(KeyFun::from_wire(7), Err(KeyFunError::Unknown(7)));
}
#[test]
fn default_is_std() {
assert_eq!(KeyFun::default(), KeyFun::Std);
}
#[test]
fn empty_inputs_are_total() {
assert_eq!(KeyFun::Std.route_bytes(b"", b""), b"/");
assert_eq!(KeyFun::BucketOnly.route_bytes(b"", b""), b"");
}
}