use std::collections::HashMap;
use serde_json::Value;
use smallvec::SmallVec;
pub const DEFAULT_CONTEXT_ROOT: &str = "main";
pub type ContextId = SmallVec<[String; 4]>;
pub fn default_context() -> ContextId {
let mut ctx = SmallVec::new();
ctx.push(DEFAULT_CONTEXT_ROOT.to_string());
ctx
}
#[derive(Debug, Clone, Default)]
pub struct Cell {
contexts: HashMap<ContextId, Value>,
default_value: Option<Value>,
is_shared: bool,
}
impl Cell {
pub fn new(default_value: Option<Value>, is_shared: bool) -> Self {
Self {
contexts: HashMap::new(),
default_value,
is_shared,
}
}
pub fn set(&mut self, context: &ContextId, value: Value) {
let key = if self.is_shared {
default_context()
} else {
context.clone()
};
self.contexts.insert(key, value);
}
pub fn get(&self, context: &ContextId) -> Option<&Value> {
let mut ctx: ContextId = if self.is_shared {
default_context()
} else {
context.clone()
};
loop {
if let Some(v) = self.contexts.get(&ctx) {
return Some(v);
}
if ctx.is_empty() {
break;
}
ctx.pop();
}
self.default_value.as_ref()
}
pub fn pop(&mut self, context: &ContextId) -> Option<Value> {
self.contexts
.remove(context)
.or_else(|| self.default_value.clone())
}
pub fn contains(&self, context: &ContextId) -> bool {
self.contexts.contains_key(context)
}
pub fn iter(&self) -> impl Iterator<Item = (&ContextId, &Value)> {
self.contexts.iter()
}
pub fn len(&self) -> usize {
self.contexts.len()
}
pub fn is_empty(&self) -> bool {
self.contexts.is_empty()
}
pub fn is_shared(&self) -> bool {
self.is_shared
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn ctx(parts: &[&str]) -> ContextId {
parts.iter().map(|s| s.to_string()).collect()
}
#[test]
fn parent_walk_finds_value_at_ancestor_context() {
let mut cell = Cell::new(None, false);
cell.set(&ctx(&["main"]), json!(42));
let result = cell.get(&ctx(&["main", "[0]", "[1]"]));
assert_eq!(result, Some(&json!(42)));
}
#[test]
fn exact_context_beats_ancestor() {
let mut cell = Cell::new(None, false);
cell.set(&ctx(&["main"]), json!("outer"));
cell.set(&ctx(&["main", "[0]"]), json!("inner"));
assert_eq!(cell.get(&ctx(&["main", "[0]"])), Some(&json!("inner")));
assert_eq!(cell.get(&ctx(&["main"])), Some(&json!("outer")));
}
#[test]
fn shared_cell_folds_writes_to_default() {
let mut cell = Cell::new(None, true);
cell.set(&ctx(&["main", "[0]"]), json!(1));
cell.set(&ctx(&["main", "[1]"]), json!(2));
assert_eq!(cell.len(), 1);
assert_eq!(cell.get(&ctx(&["main", "[0]"])), Some(&json!(2)));
assert_eq!(cell.get(&ctx(&["main", "[1]"])), Some(&json!(2)));
}
#[test]
fn default_value_returned_when_nothing_found() {
let cell = Cell::new(Some(json!("fallback")), false);
assert_eq!(cell.get(&ctx(&["main"])), Some(&json!("fallback")));
}
}