use std::collections::{HashMap, HashSet};
use serde_json::Value;
pub fn value_key(value: &Value) -> String {
match value {
Value::Null => "z:".to_string(),
Value::String(s) => format!("s:{s}"),
Value::Number(n) => format!("n:{n}"),
Value::Bool(b) => format!("b:{b}"),
other => format!("j:{other}"),
}
}
pub fn value_keys(value: Option<&Value>) -> Vec<String> {
match value {
None | Some(Value::Null) => Vec::new(),
Some(Value::Array(items)) => {
let mut seen = HashSet::new();
let mut keys = Vec::new();
for item in items {
let encoded = value_key(item);
if seen.insert(encoded.clone()) {
keys.push(encoded);
}
}
keys
}
Some(other) => vec![value_key(other)],
}
}
#[derive(Debug, Default)]
pub struct ExactIndex {
buckets: HashMap<String, HashSet<String>>,
}
impl ExactIndex {
pub fn new() -> Self {
ExactIndex {
buckets: HashMap::new(),
}
}
pub fn add(&mut self, value: String, key: String) {
self.buckets.entry(value).or_default().insert(key);
}
pub fn remove(&mut self, value: &str, key: &str) {
let Some(bucket) = self.buckets.get_mut(value) else {
return;
};
bucket.remove(key);
if bucket.is_empty() {
self.buckets.remove(value);
}
}
pub fn lookup(&self, value: &str) -> Option<&HashSet<String>> {
self.buckets.get(value)
}
pub fn holder_of(&self, value: &str) -> Option<&str> {
self.buckets.get(value)?.iter().next().map(String::as_str)
}
#[allow(dead_code)]
pub fn cardinality(&self) -> usize {
self.buckets.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adds_and_removes_a_key_from_its_bucket() {
let mut index = ExactIndex::new();
index.add("admin".into(), "user_1".into());
index.add("admin".into(), "user_2".into());
assert_eq!(index.cardinality(), 1);
index.remove("admin", "user_1");
assert_eq!(index.lookup("admin").unwrap().len(), 1);
index.remove("admin", "user_2");
assert!(index.lookup("admin").is_none());
assert_eq!(index.cardinality(), 0);
}
#[test]
fn does_not_confuse_a_number_with_its_string_form() {
let number = value_key(&Value::from(31));
let text = value_key(&Value::String("31".into()));
assert_ne!(number, text);
}
#[test]
fn dedupes_array_values_into_one_key_each() {
let value = Value::from(vec!["backend", "backend", "node"]);
assert_eq!(value_keys(Some(&value)).len(), 2);
}
#[test]
fn treats_a_missing_field_and_an_explicit_null_the_same() {
assert!(value_keys(None).is_empty());
assert!(value_keys(Some(&Value::Null)).is_empty());
}
}