use std::collections::BTreeMap;
use std::fmt;
use smallvec::SmallVec;
use super::value::{PropertyKey, Value};
const INLINE_CAP: usize = 4;
#[derive(Clone, PartialEq)]
pub struct PropertyMap {
entries: SmallVec<[(PropertyKey, Value); INLINE_CAP]>,
}
impl PropertyMap {
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
entries: SmallVec::new(),
}
}
#[inline]
#[must_use]
pub fn with_capacity(cap: usize) -> Self {
Self {
entries: SmallVec::with_capacity(cap),
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn get(&self, key: &PropertyKey) -> Option<&Value> {
self.entries.iter().find(|(k, _)| k == key).map(|(_, v)| v)
}
#[must_use]
pub fn contains_key(&self, key: &PropertyKey) -> bool {
self.entries.iter().any(|(k, _)| k == key)
}
pub fn insert(&mut self, key: PropertyKey, value: Value) -> Option<Value> {
for entry in &mut self.entries {
if entry.0 == key {
let old = std::mem::replace(&mut entry.1, value);
return Some(old);
}
}
self.entries.push((key, value));
None
}
pub fn remove(&mut self, key: &PropertyKey) -> Option<Value> {
if let Some(pos) = self.entries.iter().position(|(k, _)| k == key) {
Some(self.entries.swap_remove(pos).1)
} else {
None
}
}
pub fn iter(&self) -> impl Iterator<Item = (&PropertyKey, &Value)> {
self.entries.iter().map(|(k, v)| (k, v))
}
#[must_use]
pub fn to_btree_map(&self) -> BTreeMap<PropertyKey, Value> {
self.entries.iter().cloned().collect()
}
}
impl Default for PropertyMap {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl fmt::Debug for PropertyMap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_map()
.entries(self.entries.iter().map(|(k, v)| (k, v)))
.finish()
}
}
impl FromIterator<(PropertyKey, Value)> for PropertyMap {
fn from_iter<I: IntoIterator<Item = (PropertyKey, Value)>>(iter: I) -> Self {
let iter = iter.into_iter();
let (lower, _) = iter.size_hint();
let mut map = Self::with_capacity(lower);
for (k, v) in iter {
map.insert(k, v);
}
map
}
}
impl IntoIterator for PropertyMap {
type Item = (PropertyKey, Value);
type IntoIter = smallvec::IntoIter<[(PropertyKey, Value); INLINE_CAP]>;
fn into_iter(self) -> Self::IntoIter {
self.entries.into_iter()
}
}
impl<'a> IntoIterator for &'a PropertyMap {
type Item = (&'a PropertyKey, &'a Value);
type IntoIter = std::iter::Map<
std::slice::Iter<'a, (PropertyKey, Value)>,
fn(&'a (PropertyKey, Value)) -> (&'a PropertyKey, &'a Value),
>;
fn into_iter(self) -> Self::IntoIter {
self.entries.iter().map(|(k, v)| (k, v))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn insert_and_get() {
let mut map = PropertyMap::new();
map.insert(PropertyKey::new("name"), Value::from("Alix"));
map.insert(PropertyKey::new("age"), Value::from(30i64));
assert_eq!(map.len(), 2);
assert_eq!(
map.get(&PropertyKey::new("name")).and_then(Value::as_str),
Some("Alix")
);
assert_eq!(
map.get(&PropertyKey::new("age")).and_then(Value::as_int64),
Some(30)
);
}
#[test]
fn insert_replaces_existing() {
let mut map = PropertyMap::new();
map.insert(PropertyKey::new("x"), Value::from(1i64));
let old = map.insert(PropertyKey::new("x"), Value::from(2i64));
assert_eq!(old, Some(Value::from(1i64)));
assert_eq!(map.len(), 1);
assert_eq!(
map.get(&PropertyKey::new("x")).and_then(Value::as_int64),
Some(2)
);
}
#[test]
fn remove() {
let mut map = PropertyMap::new();
map.insert(PropertyKey::new("a"), Value::from(1i64));
map.insert(PropertyKey::new("b"), Value::from(2i64));
let removed = map.remove(&PropertyKey::new("a"));
assert_eq!(removed, Some(Value::from(1i64)));
assert_eq!(map.len(), 1);
assert!(map.get(&PropertyKey::new("a")).is_none());
assert!(map.get(&PropertyKey::new("b")).is_some());
}
#[test]
fn contains_key() {
let mut map = PropertyMap::new();
map.insert(PropertyKey::new("x"), Value::Null);
assert!(map.contains_key(&PropertyKey::new("x")));
assert!(!map.contains_key(&PropertyKey::new("y")));
}
#[test]
fn from_iterator() {
let pairs = vec![
(PropertyKey::new("a"), Value::from(1i64)),
(PropertyKey::new("b"), Value::from(2i64)),
];
let map: PropertyMap = pairs.into_iter().collect();
assert_eq!(map.len(), 2);
}
#[test]
fn into_iterator() {
let mut map = PropertyMap::new();
map.insert(PropertyKey::new("x"), Value::from(1i64));
map.insert(PropertyKey::new("y"), Value::from(2i64));
let pairs: Vec<_> = map.into_iter().collect();
assert_eq!(pairs.len(), 2);
}
#[test]
fn empty() {
let map = PropertyMap::new();
assert!(map.is_empty());
assert_eq!(map.len(), 0);
assert!(map.get(&PropertyKey::new("x")).is_none());
}
#[test]
fn inline_capacity() {
let mut map = PropertyMap::new();
for i in 0..4 {
map.insert(PropertyKey::new(format!("k{i}")), Value::from(i as i64));
}
assert_eq!(map.len(), 4);
assert!(!map.entries.spilled());
}
#[test]
fn spills_to_heap_beyond_capacity() {
let mut map = PropertyMap::new();
for i in 0..10 {
map.insert(PropertyKey::new(format!("k{i}")), Value::from(i as i64));
}
assert_eq!(map.len(), 10);
assert!(map.entries.spilled());
for i in 0..10 {
assert!(map.contains_key(&PropertyKey::new(format!("k{i}"))));
}
}
#[test]
fn debug_format() {
let mut map = PropertyMap::new();
map.insert(PropertyKey::new("name"), Value::from("Alix"));
let dbg = format!("{map:?}");
assert!(dbg.contains("name"));
}
#[test]
fn from_empty_iterator() {
let map: PropertyMap = std::iter::empty().collect();
assert!(map.is_empty());
}
#[test]
fn ref_iterator() {
let mut map = PropertyMap::new();
map.insert(PropertyKey::new("a"), Value::from(1i64));
map.insert(PropertyKey::new("b"), Value::from(2i64));
let pairs: Vec<_> = (&map).into_iter().collect();
assert_eq!(pairs.len(), 2);
}
#[test]
fn default_is_empty() {
let map = PropertyMap::default();
assert!(map.is_empty());
}
#[test]
fn remove_nonexistent() {
let mut map = PropertyMap::new();
assert_eq!(map.remove(&PropertyKey::new("missing")), None);
}
#[test]
fn to_btree_map_round_trip() {
let mut map = PropertyMap::new();
map.insert(PropertyKey::new("x"), Value::from(1i64));
map.insert(PropertyKey::new("y"), Value::from(2i64));
let btree = map.to_btree_map();
assert_eq!(btree.len(), 2);
assert_eq!(btree[&PropertyKey::new("x")], Value::from(1i64));
}
}