use bytes::Bytes;
use structfs_ll_store::{LLError, LLPath, LLReader, LLWriter};
use crate::{Codec, Error, Format, Path, PathError, Reader, Record, Writer};
pub struct LLToCore<T, C> {
inner: T,
codec: C,
read_format: Format,
write_format: Format,
}
impl<T, C> LLToCore<T, C> {
pub fn new(inner: T, codec: C, format: Format) -> Self {
Self {
inner,
codec,
read_format: format.clone(),
write_format: format,
}
}
pub fn with_formats(inner: T, codec: C, read_format: Format, write_format: Format) -> Self {
Self {
inner,
codec,
read_format,
write_format,
}
}
pub fn inner(&self) -> &T {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut T {
&mut self.inner
}
pub fn into_inner(self) -> T {
self.inner
}
}
impl<T: LLReader, C: Send + Sync> Reader for LLToCore<T, C> {
fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
let components: Vec<&[u8]> = from.as_ll().as_byte_refs();
let bytes = match self.inner.ll_read(&components) {
Ok(Some(b)) => b,
Ok(None) => return Ok(None),
Err(e) => return Err(Error::Ll(e)),
};
Ok(Some(Record::raw(bytes, self.read_format.clone())))
}
}
impl<T: LLWriter, C: Codec + Send + Sync> Writer for LLToCore<T, C> {
fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
let bytes = data.into_bytes(&self.codec, &self.write_format)?;
let components: Vec<&[u8]> = to.as_ll().as_byte_refs();
let result_path = self.inner.ll_write(&components, bytes).map_err(Error::Ll)?;
path_from_ll(&result_path)
}
}
pub struct CoreToLL<T, C> {
inner: T,
codec: C,
format: Format,
}
impl<T, C> CoreToLL<T, C> {
pub fn new(inner: T, codec: C, format: Format) -> Self {
Self {
inner,
codec,
format,
}
}
pub fn inner(&self) -> &T {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut T {
&mut self.inner
}
pub fn into_inner(self) -> T {
self.inner
}
}
impl<T: Reader, C: Codec + Send + Sync> LLReader for CoreToLL<T, C> {
fn ll_read(&mut self, path: &[&[u8]]) -> Result<Option<Bytes>, LLError> {
let path = path_from_bytes(path).map_err(|e| LLError::Protocol {
code: 1,
detail: Bytes::copy_from_slice(e.to_string().as_bytes()),
})?;
let record = match self.inner.read(&path) {
Ok(Some(r)) => r,
Ok(None) => return Ok(None),
Err(e) => {
return Err(LLError::Protocol {
code: 2,
detail: Bytes::copy_from_slice(e.to_string().as_bytes()),
})
}
};
let bytes =
record
.into_bytes(&self.codec, &self.format)
.map_err(|e| LLError::Protocol {
code: 3,
detail: Bytes::copy_from_slice(e.to_string().as_bytes()),
})?;
Ok(Some(bytes))
}
}
impl<T: Writer, C: Send + Sync> LLWriter for CoreToLL<T, C> {
fn ll_write(&mut self, path: &[&[u8]], data: Bytes) -> Result<LLPath, LLError> {
let path = path_from_bytes(path).map_err(|e| LLError::Protocol {
code: 1,
detail: Bytes::copy_from_slice(e.to_string().as_bytes()),
})?;
let record = Record::raw(data, self.format.clone());
let result_path = self
.inner
.write(&path, record)
.map_err(|e| LLError::Protocol {
code: 2,
detail: Bytes::copy_from_slice(e.to_string().as_bytes()),
})?;
Ok(result_path.into_ll())
}
}
pub(crate) fn path_from_bytes(components: &[&[u8]]) -> Result<Path, PathError> {
let mut strings = Vec::with_capacity(components.len());
for (i, bytes) in components.iter().enumerate() {
let s = std::str::from_utf8(bytes).map_err(|_| PathError::InvalidComponent {
component: format!("{:?}", bytes),
position: i,
message: "not valid UTF-8".to_string(),
})?;
strings.push(s.to_string());
}
Path::try_from_components(strings)
}
pub(crate) fn path_from_ll(components: &[Bytes]) -> Result<Path, Error> {
let refs: Vec<&[u8]> = components.iter().map(|b| b.as_ref()).collect();
path_from_bytes(&refs).map_err(Error::Path)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{path, NoCodec};
use std::collections::HashMap;
struct TestLLStore {
data: HashMap<Vec<Vec<u8>>, Bytes>,
}
impl TestLLStore {
fn new() -> Self {
Self {
data: HashMap::new(),
}
}
}
impl LLReader for TestLLStore {
fn ll_read(&mut self, path: &[&[u8]]) -> Result<Option<Bytes>, LLError> {
let key: Vec<Vec<u8>> = path.iter().map(|c| c.to_vec()).collect();
Ok(self.data.get(&key).cloned())
}
}
impl LLWriter for TestLLStore {
fn ll_write(&mut self, path: &[&[u8]], data: Bytes) -> Result<LLPath, LLError> {
let key: Vec<Vec<u8>> = path.iter().map(|c| c.to_vec()).collect();
self.data.insert(key, data);
Ok(path.iter().map(|c| Bytes::copy_from_slice(c)).collect())
}
}
struct TestCoreStore {
data: HashMap<Path, Record>,
}
impl TestCoreStore {
fn new() -> Self {
Self {
data: HashMap::new(),
}
}
}
impl Reader for TestCoreStore {
fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
Ok(self.data.get(from).cloned())
}
}
impl Writer for TestCoreStore {
fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
self.data.insert(to.clone(), data);
Ok(to.clone())
}
}
#[test]
fn ll_to_core_read() {
let mut ll = TestLLStore::new();
ll.data.insert(
vec![b"users".to_vec(), b"123".to_vec()],
Bytes::from_static(b"hello"),
);
let mut bridge = LLToCore::new(ll, NoCodec, Format::OCTET_STREAM);
let result = bridge.read(&path!("users/123")).unwrap();
assert!(result.is_some());
assert_eq!(
result.unwrap().as_bytes(),
Some(&Bytes::from_static(b"hello"))
);
}
#[test]
fn ll_to_core_write() {
let ll = TestLLStore::new();
let mut bridge = LLToCore::new(ll, NoCodec, Format::OCTET_STREAM);
let record = Record::raw(Bytes::from_static(b"data"), Format::OCTET_STREAM);
bridge.write(&path!("test/path"), record).unwrap();
let key = vec![b"test".to_vec(), b"path".to_vec()];
assert!(bridge.inner().data.contains_key(&key));
}
#[test]
fn core_to_ll_read() {
let mut core = TestCoreStore::new();
core.data.insert(
path!("users/123"),
Record::raw(Bytes::from_static(b"hello"), Format::OCTET_STREAM),
);
let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
let result = bridge.ll_read(&[b"users", b"123"]).unwrap();
assert_eq!(result, Some(Bytes::from_static(b"hello")));
}
#[test]
fn core_to_ll_write() {
let core = TestCoreStore::new();
let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
bridge
.ll_write(&[b"test", b"path"], Bytes::from_static(b"data"))
.unwrap();
assert!(bridge.inner().data.contains_key(&path!("test/path")));
}
#[test]
fn invalid_utf8_path_rejected() {
let core = TestCoreStore::new();
let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
let result = bridge.ll_read(&[&[0xFF, 0xFE]]);
assert!(matches!(result, Err(LLError::Protocol { .. })));
}
#[test]
fn ll_to_core_with_formats() {
let ll = TestLLStore::new();
let bridge = LLToCore::with_formats(ll, NoCodec, Format::JSON, Format::OCTET_STREAM);
assert_eq!(bridge.read_format, Format::JSON);
assert_eq!(bridge.write_format, Format::OCTET_STREAM);
}
#[test]
fn ll_to_core_inner_methods() {
let ll = TestLLStore::new();
let mut bridge = LLToCore::new(ll, NoCodec, Format::OCTET_STREAM);
assert!(bridge.inner().data.is_empty());
bridge
.inner_mut()
.data
.insert(vec![b"key".to_vec()], Bytes::from_static(b"value"));
assert!(!bridge.inner().data.is_empty());
let ll = bridge.into_inner();
assert!(!ll.data.is_empty());
}
#[test]
fn core_to_ll_inner_methods() {
let core = TestCoreStore::new();
let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
assert!(bridge.inner().data.is_empty());
bridge.inner_mut().data.insert(
path!("key"),
Record::raw(Bytes::from_static(b"value"), Format::OCTET_STREAM),
);
assert!(!bridge.inner().data.is_empty());
let core = bridge.into_inner();
assert!(!core.data.is_empty());
}
#[test]
fn ll_to_core_read_none() {
let ll = TestLLStore::new();
let mut bridge = LLToCore::new(ll, NoCodec, Format::OCTET_STREAM);
let result = bridge.read(&path!("nonexistent")).unwrap();
assert!(result.is_none());
}
#[test]
fn core_to_ll_read_none() {
let core = TestCoreStore::new();
let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
let result = bridge.ll_read(&[b"nonexistent"]).unwrap();
assert!(result.is_none());
}
#[test]
fn core_to_ll_write_invalid_utf8() {
let core = TestCoreStore::new();
let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
let result = bridge.ll_write(&[&[0xFF, 0xFE]], Bytes::from_static(b"data"));
assert!(matches!(result, Err(LLError::Protocol { code: 1, .. })));
}
#[test]
fn path_from_bytes_empty() {
let result = path_from_bytes(&[]).unwrap();
assert!(result.is_empty());
}
#[test]
fn path_from_bytes_single_component() {
let result = path_from_bytes(&[b"users"]).unwrap();
assert_eq!(result.to_string(), "users");
}
#[test]
fn path_from_bytes_multiple_components() {
let result = path_from_bytes(&[b"users", b"123", b"profile"]).unwrap();
assert_eq!(result.to_string(), "users/123/profile");
}
#[test]
fn path_from_ll_works() {
let ll_path = vec![Bytes::from_static(b"a"), Bytes::from_static(b"b")];
let result = path_from_ll(&ll_path).unwrap();
assert_eq!(result.to_string(), "a/b");
}
#[test]
fn path_from_ll_invalid_utf8() {
let ll_path = vec![Bytes::from_static(&[0xFF, 0xFE])];
let result = path_from_ll(&ll_path);
assert!(result.is_err());
}
struct ErrorCoreStore;
impl Reader for ErrorCoreStore {
fn read(&mut self, _from: &Path) -> Result<Option<Record>, Error> {
Err(Error::store("test", "read", "read error"))
}
}
impl Writer for ErrorCoreStore {
fn write(&mut self, _to: &Path, _data: Record) -> Result<Path, Error> {
Err(Error::store("test", "write", "write error"))
}
}
#[test]
fn core_to_ll_read_error() {
let core = ErrorCoreStore;
let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
let result = bridge.ll_read(&[b"any"]);
assert!(matches!(result, Err(LLError::Protocol { code: 2, .. })));
}
#[test]
fn core_to_ll_write_error() {
let core = ErrorCoreStore;
let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
let result = bridge.ll_write(&[b"any"], Bytes::from_static(b"data"));
assert!(matches!(result, Err(LLError::Protocol { code: 2, .. })));
}
struct ErrorLLStore;
impl LLReader for ErrorLLStore {
fn ll_read(&mut self, _path: &[&[u8]]) -> Result<Option<Bytes>, LLError> {
Err(LLError::Protocol {
code: 99,
detail: Bytes::from_static(b"ll error"),
})
}
}
impl LLWriter for ErrorLLStore {
fn ll_write(&mut self, _path: &[&[u8]], _data: Bytes) -> Result<LLPath, LLError> {
Err(LLError::Protocol {
code: 99,
detail: Bytes::from_static(b"ll write error"),
})
}
}
#[test]
fn ll_to_core_read_error() {
let ll = ErrorLLStore;
let mut bridge = LLToCore::new(ll, NoCodec, Format::OCTET_STREAM);
let result = bridge.read(&path!("any"));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("ll error"));
}
#[test]
fn ll_to_core_write_error() {
let ll = ErrorLLStore;
let mut bridge = LLToCore::new(ll, NoCodec, Format::OCTET_STREAM);
let result = bridge.write(
&path!("any"),
Record::raw(Bytes::from_static(b"data"), Format::OCTET_STREAM),
);
assert!(result.is_err());
}
}