use std::sync::Arc;
use rustc_hash::FxBuildHasher;
use crate::ClusterId;
#[derive(Debug)]
pub(crate) struct LeafBucket {
cap: usize,
ids: Vec<ClusterId>,
}
impl LeafBucket {
pub(crate) fn new(cap: usize) -> Self {
LeafBucket {
cap,
ids: Vec::new(),
}
}
pub(crate) fn ids(&self) -> &[ClusterId] {
&self.ids
}
pub(crate) fn is_full(&self) -> bool {
self.ids.len() >= self.cap
}
pub(crate) fn insert(&mut self, id: ClusterId) {
self.ids.push(id);
}
pub(crate) fn remove(&mut self, id: ClusterId) {
if let Some(pos) = self.ids.iter().position(|&x| x == id) {
self.ids.swap_remove(pos);
}
}
}
#[derive(Debug)]
pub(crate) enum TreeNode {
Internal {
children: std::collections::HashMap<Arc<str>, TreeNode, FxBuildHasher>,
},
Leaf(LeafBucket),
}
impl TreeNode {
pub(crate) fn new_internal() -> Self {
TreeNode::Internal {
children: std::collections::HashMap::with_hasher(FxBuildHasher),
}
}
pub(crate) fn descend_or_create(&mut self, keys: &[&str], leaf_cap: usize) -> &mut LeafBucket {
match keys.split_first() {
None => {
if !matches!(self, TreeNode::Leaf(_)) {
*self = TreeNode::Leaf(LeafBucket::new(leaf_cap));
}
match self {
TreeNode::Leaf(b) => b,
TreeNode::Internal { .. } => unreachable!(),
}
}
Some((head, rest)) => {
let children = match self {
TreeNode::Internal { children } => children,
TreeNode::Leaf(_) => unreachable!("internal/leaf depth is fixed per shard"),
};
if !children.contains_key(*head) {
let node = if rest.is_empty() {
TreeNode::Leaf(LeafBucket::new(leaf_cap))
} else {
TreeNode::new_internal()
};
children.insert(Arc::from(*head), node);
}
children
.get_mut(*head)
.expect("child just inserted or already present")
.descend_or_create(rest, leaf_cap)
}
}
}
pub(crate) fn descend(&self, keys: &[&str]) -> Option<&LeafBucket> {
match keys.split_first() {
None => match self {
TreeNode::Leaf(b) => Some(b),
TreeNode::Internal { .. } => None,
},
Some((head, rest)) => match self {
TreeNode::Internal { children } => children.get(*head)?.descend(rest),
TreeNode::Leaf(_) => None,
},
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn leaf_membership_and_capacity() {
let mut leaf = LeafBucket::new(2);
assert!(!leaf.is_full());
leaf.insert(10);
leaf.insert(20);
assert!(leaf.is_full());
let mut ids = leaf.ids().to_vec();
ids.sort_unstable();
assert_eq!(ids, vec![10, 20]);
leaf.remove(10);
assert!(!leaf.is_full());
assert_eq!(leaf.ids(), &[20]);
}
#[test]
fn descend_creates_path_and_returns_leaf() {
let mut root = TreeNode::new_internal();
let keys = ["GET", "/api"];
root.descend_or_create(&keys, 100).insert(1);
let leaf2 = root.descend_or_create(&keys, 100);
assert_eq!(leaf2.ids(), &[1]);
}
#[test]
fn descend_readonly_misses_on_absent_path() {
let mut root = TreeNode::new_internal();
let keys = ["GET"];
root.descend_or_create(&keys, 100).insert(1);
assert!(root.descend(&["POST"]).is_none());
assert!(root.descend(&keys).is_some());
}
#[test]
fn empty_keys_make_root_a_leaf() {
let mut root = TreeNode::new_internal();
let keys: [&str; 0] = [];
root.descend_or_create(&keys, 5).insert(1);
assert_eq!(root.descend(&keys).unwrap().ids(), &[1]);
}
}