Skip to main content

structfs_ll_store/
lib.rs

1//! LLStructFS: Low-Level StructFS Store Traits
2//!
3//! This is the narrow waist of the StructFS stack. Everything at this level is
4//! pure bytes - no path validation, no value semantics, no format interpretation.
5//!
6//! Use this layer for:
7//! - WASM/FFI boundaries where you're marshalling raw memory
8//! - Wire protocols where you're moving bytes without inspection
9//! - Zero-copy forwarding proxies
10//! - Any transport that shouldn't pay parsing costs
11//!
12//! # Example
13//!
14//! ```rust
15//! use structfs_ll_store::{LLReader, LLWriter, LLError};
16//! use bytes::Bytes;
17//!
18//! struct InMemoryLLStore {
19//!     data: std::collections::HashMap<Vec<Vec<u8>>, Bytes>,
20//! }
21//!
22//! impl LLReader for InMemoryLLStore {
23//!     fn ll_read(&mut self, path: &[&[u8]]) -> Result<Option<Bytes>, LLError> {
24//!         let key: Vec<Vec<u8>> = path.iter().map(|c| c.to_vec()).collect();
25//!         Ok(self.data.get(&key).cloned())
26//!     }
27//! }
28//! ```
29//!
30//! # Async Support
31//!
32//! Enable the `async` feature for async trait variants:
33//!
34//! ```toml
35//! [dependencies]
36//! structfs-ll-store = { version = "0.1", features = ["async"] }
37//! ```
38//!
39//! Then use `AsyncLLReader`, `AsyncLLWriter`, and `AsyncLLStore`.
40
41pub use bytes::Bytes;
42
43mod error;
44mod traits;
45
46pub use error::LLError;
47pub use traits::{LLPath, LLReader, LLStore, LLWriter};
48
49#[cfg(feature = "async")]
50mod async_traits;
51
52#[cfg(feature = "async")]
53pub use async_traits::{AsyncLLReader, AsyncLLStore, AsyncLLWriter, SyncToAsyncLL};
54
55/// Convenience function to create an owned path from byte slices.
56pub fn ll_path(components: &[&[u8]]) -> LLPath {
57    components
58        .iter()
59        .map(|c| Bytes::copy_from_slice(c))
60        .collect()
61}
62
63/// Convenience function to create an owned path from string slices.
64pub fn ll_path_from_strs(components: &[&str]) -> LLPath {
65    components
66        .iter()
67        .map(|s| Bytes::copy_from_slice(s.as_bytes()))
68        .collect()
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn ll_path_creates_owned_path() {
77        let path = ll_path(&[b"users", b"123"]);
78        assert_eq!(path.len(), 2);
79        assert_eq!(path[0].as_ref(), b"users");
80        assert_eq!(path[1].as_ref(), b"123");
81    }
82
83    #[test]
84    fn ll_path_from_strs_creates_owned_path() {
85        let path = ll_path_from_strs(&["users", "alice"]);
86        assert_eq!(path.len(), 2);
87        assert_eq!(path[0].as_ref(), b"users");
88        assert_eq!(path[1].as_ref(), b"alice");
89    }
90
91    #[test]
92    fn ll_path_empty() {
93        let path = ll_path(&[]);
94        assert!(path.is_empty());
95    }
96
97    #[test]
98    fn ll_path_from_strs_empty() {
99        let path = ll_path_from_strs(&[]);
100        assert!(path.is_empty());
101    }
102
103    #[test]
104    fn llpath_newtype_construction_and_views() {
105        // from_components / components / into_components round-trip.
106        let raw = vec![Bytes::from_static(b"a"), Bytes::from_static(b"b")];
107        let path = LLPath::from_components(raw.clone());
108        assert_eq!(path.components(), raw.as_slice());
109        assert_eq!(path.clone().into_components(), raw);
110
111        // Deref gives slice access (len / index / iter) regardless of layout.
112        assert_eq!(path.len(), 2);
113        assert_eq!(path[0].as_ref(), b"a");
114        assert_eq!(path.iter().count(), 2);
115
116        // as_byte_refs borrows without copying bytes -- the shape the
117        // `&[&[u8]]` read/write interface consumes.
118        assert_eq!(path.as_byte_refs(), vec![b"a".as_ref(), b"b".as_ref()]);
119    }
120
121    #[test]
122    fn llpath_from_iter_and_into_iter() {
123        // FromIterator<Bytes>: the `.collect()` the constructors rely on.
124        let path: LLPath = [Bytes::from_static(b"x"), Bytes::from_static(b"y")]
125            .into_iter()
126            .collect();
127        assert_eq!(path.len(), 2);
128
129        // IntoIterator (owned): the shape featherweight lowers back to the wire.
130        let components: Vec<Vec<u8>> = path.into_iter().map(|b| b.to_vec()).collect();
131        assert_eq!(components, vec![b"x".to_vec(), b"y".to_vec()]);
132    }
133
134    #[test]
135    fn llpath_push_grows() {
136        let mut path = LLPath::new();
137        assert!(path.is_empty());
138        path.push(Bytes::from_static(b"only"));
139        assert_eq!(path.len(), 1);
140        assert_eq!(path[0].as_ref(), b"only");
141    }
142}