use std::sync::Arc;
use arora_types::data::{DataError, DataStore, Key, Slot, State, StateChange, Subscription};
use arora_types::value::Value;
#[derive(Clone)]
pub struct NamespacedStore {
inner: Arc<dyn DataStore>,
namespace: String,
}
impl NamespacedStore {
pub fn new(inner: Arc<dyn DataStore>, namespace: impl Into<String>) -> Self {
Self {
inner,
namespace: namespace.into(),
}
}
pub fn namespace(&self) -> &str {
&self.namespace
}
fn prefixed(&self, key: &Key) -> Key {
Key::from(format!("{}/{}", self.namespace, key.path))
}
}
impl DataStore for NamespacedStore {
fn read(&self, keys: &[Key]) -> Vec<Option<Value>> {
let prefixed: Vec<Key> = keys.iter().map(|k| self.prefixed(k)).collect();
self.inner.read(&prefixed)
}
fn write(&self, changes: StateChange) -> Result<(), DataError> {
let set = changes
.set
.iter()
.map(|(k, v)| (self.prefixed(k), v.clone()))
.collect();
let unset = changes.unset.iter().map(|k| self.prefixed(k)).collect();
self.inner.write(StateChange { set, unset })
}
fn slot(&self, key: &Key) -> Box<dyn Slot> {
self.inner.slot(&self.prefixed(key))
}
fn snapshot(&self) -> State {
self.inner.snapshot()
}
fn subscribe(&self) -> Subscription {
self.inner.subscribe()
}
fn clone_box(&self) -> Box<dyn DataStore> {
Box::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::SimpleDataStore;
#[test]
fn write_lands_under_namespace_in_inner_store() {
let shared = SimpleDataStore::new();
let view = NamespacedStore::new(Arc::new(shared.clone()), "robotA");
view.write(StateChange::set("joint1.position", Value::Boolean(true)))
.unwrap();
assert_eq!(
shared.read(&[Key::from("robotA/joint1.position")]),
vec![Some(Value::Boolean(true))]
);
assert_eq!(shared.read(&[Key::from("joint1.position")]), vec![None]);
}
#[test]
fn read_round_trips_device_relative_keys() {
let shared = SimpleDataStore::new();
let view = NamespacedStore::new(Arc::new(shared.clone()), "robotA");
view.write(StateChange::set("battery_level", Value::Boolean(false)))
.unwrap();
assert_eq!(
view.read(&[Key::from("battery_level")]),
vec![Some(Value::Boolean(false))]
);
}
#[test]
fn slot_round_trips_and_coincides_with_inner() {
let shared = SimpleDataStore::new();
let view = NamespacedStore::new(Arc::new(shared.clone()), "robotA");
let slot = view.slot(&Key::from("joint1.position"));
slot.set(Some(Value::Boolean(true))).unwrap();
assert_eq!(slot.get(), Some(Value::Boolean(true)));
assert_eq!(
shared.read(&[Key::from("robotA/joint1.position")]),
vec![Some(Value::Boolean(true))]
);
}
#[test]
fn two_namespaces_over_one_inner_store_do_not_collide() {
let shared = SimpleDataStore::new();
let a = NamespacedStore::new(Arc::new(shared.clone()), "robotA");
let b = NamespacedStore::new(Arc::new(shared.clone()), "robotB");
a.write(StateChange::set("joint1.position", Value::Boolean(true)))
.unwrap();
b.write(StateChange::set("joint1.position", Value::Boolean(false)))
.unwrap();
assert_eq!(
a.read(&[Key::from("joint1.position")]),
vec![Some(Value::Boolean(true))]
);
assert_eq!(
b.read(&[Key::from("joint1.position")]),
vec![Some(Value::Boolean(false))]
);
assert_eq!(
shared.read(&[
Key::from("robotA/joint1.position"),
Key::from("robotB/joint1.position"),
]),
vec![Some(Value::Boolean(true)), Some(Value::Boolean(false))]
);
}
}