use bytes::Bytes;
use crate::{Error, Format, Path, Record, Value};
pub trait Reader: Send + Sync {
fn read(&mut self, from: &Path) -> Result<Option<Record>, Error>;
fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
let Some(record) = self.read(from)? else {
return Ok(None);
};
match record.as_value() {
Some(Value::Map(map)) => Ok(Some(map.keys().cloned().collect())),
Some(Value::Array(arr)) => Ok(Some((0..arr.len()).map(|i| i.to_string()).collect())),
Some(_) => Ok(Some(Vec::new())),
None => Err(Error::store(
"reader",
"read_children",
"cannot enumerate children of a raw record; the store must override read_children",
)),
}
}
}
pub trait Writer: Send + Sync {
fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error>;
}
pub trait Store: Reader + Writer {}
impl<T: Reader + Writer> Store for T {}
pub trait Codec: Send + Sync {
fn decode(&self, bytes: &Bytes, format: &Format) -> Result<Value, Error>;
fn encode(&self, value: &Value, format: &Format) -> Result<Bytes, Error>;
fn supports(&self, format: &Format) -> bool;
}
pub struct NoCodec;
impl Codec for NoCodec {
fn decode(&self, _bytes: &Bytes, format: &Format) -> Result<Value, Error> {
Err(Error::UnsupportedFormat(format.clone()))
}
fn encode(&self, _value: &Value, format: &Format) -> Result<Bytes, Error> {
Err(Error::UnsupportedFormat(format.clone()))
}
fn supports(&self, _format: &Format) -> bool {
false
}
}
impl<T: Reader + ?Sized> Reader for &mut T {
fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
(*self).read(from)
}
fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
(*self).read_children(from)
}
}
impl<T: Writer + ?Sized> Writer for &mut T {
fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
(*self).write(to, data)
}
}
impl<T: Reader + ?Sized> Reader for Box<T> {
fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
self.as_mut().read(from)
}
fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
self.as_mut().read_children(from)
}
}
impl<T: Writer + ?Sized> Writer for Box<T> {
fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
self.as_mut().write(to, data)
}
}
impl<T: Codec + ?Sized> Codec for Box<T> {
fn decode(&self, bytes: &Bytes, format: &Format) -> Result<Value, Error> {
self.as_ref().decode(bytes, format)
}
fn encode(&self, value: &Value, format: &Format) -> Result<Bytes, Error> {
self.as_ref().encode(value, format)
}
fn supports(&self, format: &Format) -> bool {
self.as_ref().supports(format)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
struct TestStore {
data: HashMap<Path, Record>,
}
impl TestStore {
fn new() -> Self {
Self {
data: HashMap::new(),
}
}
}
impl Reader for TestStore {
fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
Ok(self.data.get(from).cloned())
}
}
impl Writer for TestStore {
fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
self.data.insert(to.clone(), data);
Ok(to.clone())
}
}
#[test]
fn basic_store_works() {
use crate::path;
let mut store = TestStore::new();
let path = path!("users/123");
let record = Record::parsed(Value::from("Alice"));
store.write(&path, record.clone()).unwrap();
let result = store.read(&path).unwrap();
assert!(result.is_some());
}
#[test]
fn object_safety_works() {
use crate::path;
let mut store = TestStore::new();
let boxed: &mut dyn Store = &mut store;
let path = path!("test");
boxed
.write(&path, Record::parsed(Value::from("hello")))
.unwrap();
let result = boxed.read(&path).unwrap();
assert!(result.is_some());
}
#[test]
fn no_codec_decode_fails() {
let codec = NoCodec;
let bytes = Bytes::from_static(b"hello");
let result = codec.decode(&bytes, &Format::JSON);
assert!(matches!(result, Err(Error::UnsupportedFormat(_))));
}
#[test]
fn no_codec_encode_fails() {
let codec = NoCodec;
let value = Value::from("test");
let result = codec.encode(&value, &Format::JSON);
assert!(matches!(result, Err(Error::UnsupportedFormat(_))));
}
#[test]
fn no_codec_supports_nothing() {
let codec = NoCodec;
assert!(!codec.supports(&Format::JSON));
assert!(!codec.supports(&Format::PROTOBUF));
assert!(!codec.supports(&Format::OCTET_STREAM));
}
#[test]
fn ref_mut_reader_works() {
use crate::path;
let mut store = TestStore::new();
let path = path!("test");
store
.write(&path, Record::parsed(Value::from("value")))
.unwrap();
let store_ref: &mut TestStore = &mut store;
let result = store_ref.read(&path).unwrap();
assert!(result.is_some());
}
#[test]
fn ref_mut_writer_works() {
use crate::path;
let mut store = TestStore::new();
let store_ref: &mut TestStore = &mut store;
let path = path!("test");
let result = store_ref.write(&path, Record::parsed(Value::from("data")));
assert!(result.is_ok());
let read_result = store.read(&path).unwrap();
assert!(read_result.is_some());
}
#[test]
fn boxed_reader_works() {
use crate::path;
let mut store = TestStore::new();
let path = path!("boxed_test");
store
.write(&path, Record::parsed(Value::from("boxed_value")))
.unwrap();
let mut boxed: Box<TestStore> = Box::new(store);
let result = boxed.read(&path).unwrap();
assert!(result.is_some());
}
#[test]
fn boxed_writer_works() {
use crate::path;
let store = TestStore::new();
let mut boxed: Box<TestStore> = Box::new(store);
let path = path!("boxed_write");
let result = boxed.write(&path, Record::parsed(Value::from("data")));
assert!(result.is_ok());
let read_result = boxed.read(&path).unwrap();
assert!(read_result.is_some());
}
#[test]
fn boxed_codec_works() {
struct TestCodec;
impl Codec for TestCodec {
fn decode(&self, bytes: &Bytes, _format: &Format) -> Result<Value, Error> {
let s = String::from_utf8_lossy(bytes);
Ok(Value::String(s.to_string()))
}
fn encode(&self, value: &Value, _format: &Format) -> Result<Bytes, Error> {
match value {
Value::String(s) => Ok(Bytes::from(s.clone())),
_ => Err(Error::encode(Format::OCTET_STREAM, "only strings")),
}
}
fn supports(&self, format: &Format) -> bool {
format == &Format::OCTET_STREAM
}
}
let boxed: Box<dyn Codec> = Box::new(TestCodec);
assert!(boxed.supports(&Format::OCTET_STREAM));
assert!(!boxed.supports(&Format::JSON));
let decoded = boxed
.decode(&Bytes::from_static(b"hello"), &Format::OCTET_STREAM)
.unwrap();
assert_eq!(decoded, Value::String("hello".to_string()));
let encoded = boxed
.encode(&Value::String("world".to_string()), &Format::OCTET_STREAM)
.unwrap();
assert_eq!(encoded.as_ref(), b"world");
}
#[test]
fn store_trait_auto_impl() {
fn requires_store<S: Store>(_s: &mut S) {}
let mut store = TestStore::new();
requires_store(&mut store); }
#[test]
fn read_missing_returns_none() {
use crate::path;
let mut store = TestStore::new();
let result = store.read(&path!("nonexistent")).unwrap();
assert!(result.is_none());
}
#[test]
fn read_children_default_impl() {
use crate::path;
use std::collections::BTreeMap;
let mut store = TestStore::new();
let mut map = BTreeMap::new();
map.insert("alice".to_string(), Value::from(1i64));
map.insert("bob".to_string(), Value::from(2i64));
store
.write(&path!("users"), Record::parsed(Value::Map(map)))
.unwrap();
assert_eq!(
store.read_children(&path!("users")).unwrap(),
Some(vec!["alice".to_string(), "bob".to_string()])
);
store
.write(
&path!("items"),
Record::parsed(Value::Array(vec![Value::from("a"), Value::from("b")])),
)
.unwrap();
assert_eq!(
store.read_children(&path!("items")).unwrap(),
Some(vec!["0".to_string(), "1".to_string()])
);
store
.write(&path!("leaf"), Record::parsed(Value::from("scalar")))
.unwrap();
assert_eq!(store.read_children(&path!("leaf")).unwrap(), Some(vec![]));
assert_eq!(store.read_children(&path!("missing")).unwrap(), None);
}
#[test]
fn read_children_raw_record_errors() {
use crate::path;
let mut store = TestStore::new();
store
.write(
&path!("raw"),
Record::raw(Bytes::from_static(b"{}"), Format::JSON),
)
.unwrap();
assert!(store.read_children(&path!("raw")).is_err());
}
#[test]
fn read_children_delegates_through_wrappers() {
use crate::path;
struct ListingStore;
impl Reader for ListingStore {
fn read(&mut self, _from: &Path) -> Result<Option<Record>, Error> {
Ok(None)
}
fn read_children(&mut self, _from: &Path) -> Result<Option<Vec<String>>, Error> {
Ok(Some(vec!["custom".to_string()]))
}
}
let mut store = ListingStore;
let by_ref: &mut dyn Reader = &mut store;
assert_eq!(
by_ref.read_children(&path!("x")).unwrap(),
Some(vec!["custom".to_string()])
);
let mut boxed: Box<dyn Reader> = Box::new(ListingStore);
assert_eq!(
boxed.read_children(&path!("x")).unwrap(),
Some(vec!["custom".to_string()])
);
}
}