Skip to main content

structfs_ll_store/
traits.rs

1//! Core traits for the LL layer.
2
3use bytes::Bytes;
4
5use crate::LLError;
6
7/// An owned path at the LL level: a sequence of **opaque byte components**.
8///
9/// This is the low-level path contract, made concrete as a type rather than a
10/// bare `Vec<Bytes>` alias. Each component is a `Bytes` (reference-counted,
11/// zero-copy sliceable). **No validation is performed** — components are
12/// arbitrary byte sequences with no UTF-8 or grammar requirement.
13///
14/// The high-level `Path` (in `structfs_core_store`) is a *validated refinement*
15/// of this type: every `Path` widens to an `LLPath` for free, and an `LLPath`
16/// narrows to a `Path` only by validation. Keeping the two contracts as
17/// distinct types is what lets the widening direction stay zero-cost and puts
18/// validation at exactly one narrowing boundary.
19///
20/// The inner representation is intentionally private so it can later change
21/// (e.g. to a single flat buffer with component offsets) without touching call
22/// sites — `Deref<Target = [Bytes]>` plus the iterator impls keep the common
23/// read/iterate/collect patterns working regardless of the backing layout.
24#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
25pub struct LLPath(Vec<Bytes>);
26
27impl LLPath {
28    /// An empty path (zero components).
29    pub fn new() -> Self {
30        LLPath(Vec::new())
31    }
32
33    /// Build a path from owned byte components.
34    pub fn from_components(components: Vec<Bytes>) -> Self {
35        LLPath(components)
36    }
37
38    /// The components as a slice.
39    pub fn components(&self) -> &[Bytes] {
40        &self.0
41    }
42
43    /// Consume into the underlying component vector.
44    pub fn into_components(self) -> Vec<Bytes> {
45        self.0
46    }
47
48    /// Append a component.
49    pub fn push(&mut self, component: Bytes) {
50        self.0.push(component);
51    }
52
53    /// Borrow each component as a byte slice, for passing to the `&[&[u8]]`
54    /// read/write interface without copying any bytes.
55    pub fn as_byte_refs(&self) -> Vec<&[u8]> {
56        self.0.iter().map(|c| c.as_ref()).collect()
57    }
58}
59
60impl std::ops::Deref for LLPath {
61    type Target = [Bytes];
62    fn deref(&self) -> &[Bytes] {
63        &self.0
64    }
65}
66
67impl FromIterator<Bytes> for LLPath {
68    fn from_iter<I: IntoIterator<Item = Bytes>>(iter: I) -> Self {
69        LLPath(iter.into_iter().collect())
70    }
71}
72
73impl IntoIterator for LLPath {
74    type Item = Bytes;
75    type IntoIter = std::vec::IntoIter<Bytes>;
76    fn into_iter(self) -> Self::IntoIter {
77        self.0.into_iter()
78    }
79}
80
81impl<'a> IntoIterator for &'a LLPath {
82    type Item = &'a Bytes;
83    type IntoIter = std::slice::Iter<'a, Bytes>;
84    fn into_iter(self) -> Self::IntoIter {
85        self.0.iter()
86    }
87}
88
89impl From<Vec<Bytes>> for LLPath {
90    fn from(components: Vec<Bytes>) -> Self {
91        LLPath(components)
92    }
93}
94
95/// Read bytes from a path.
96///
97/// This is the lowest-level read interface. Paths are just byte sequences,
98/// and the returned data is just bytes. No parsing, no validation.
99///
100/// # Object Safety
101///
102/// This trait is object-safe: you can use `Box<dyn LLReader>`.
103pub trait LLReader: Send + Sync {
104    /// Read raw bytes from path components.
105    ///
106    /// # Arguments
107    ///
108    /// * `path` - A slice of byte slices representing path components.
109    ///   No validation is performed - components are opaque bytes.
110    ///
111    /// # Returns
112    ///
113    /// * `Ok(None)` - The path does not exist (not an error condition).
114    /// * `Ok(Some(bytes))` - The data at the path.
115    /// * `Err(LLError)` - A transport or system error occurred.
116    ///
117    /// # Example
118    ///
119    /// ```rust
120    /// use structfs_ll_store::{LLReader, LLError};
121    /// use bytes::Bytes;
122    ///
123    /// fn read_user(store: &mut dyn LLReader, user_id: &str) -> Result<Option<Bytes>, LLError> {
124    ///     store.ll_read(&[b"users", user_id.as_bytes()])
125    /// }
126    /// ```
127    fn ll_read(&mut self, path: &[&[u8]]) -> Result<Option<Bytes>, LLError>;
128}
129
130/// Write bytes to a path.
131///
132/// This is the lowest-level write interface. Paths and data are just bytes.
133/// No parsing, no validation.
134///
135/// # Object Safety
136///
137/// This trait is object-safe: you can use `Box<dyn LLWriter>`.
138pub trait LLWriter: Send + Sync {
139    /// Write raw bytes to path components.
140    ///
141    /// # Arguments
142    ///
143    /// * `path` - A slice of byte slices representing path components.
144    /// * `data` - The bytes to write.
145    ///
146    /// # Returns
147    ///
148    /// The "result path" as a sequence of byte components. This may be:
149    /// - The same as the input path (for simple stores)
150    /// - A different path (e.g., a generated ID, a handle for async operations)
151    ///
152    /// # Example
153    ///
154    /// ```rust
155    /// use structfs_ll_store::{LLWriter, LLPath, LLError};
156    /// use bytes::Bytes;
157    ///
158    /// fn create_user(store: &mut dyn LLWriter, data: &[u8]) -> Result<LLPath, LLError> {
159    ///     store.ll_write(&[b"users"], Bytes::copy_from_slice(data))
160    /// }
161    /// ```
162    fn ll_write(&mut self, path: &[&[u8]], data: Bytes) -> Result<LLPath, LLError>;
163}
164
165/// Combined read/write at the LL level.
166///
167/// This is a convenience trait for stores that support both reading and writing.
168/// It is automatically implemented for any type that implements both `LLReader`
169/// and `LLWriter`.
170pub trait LLStore: LLReader + LLWriter {}
171impl<T: LLReader + LLWriter> LLStore for T {}
172
173// Blanket implementations for references and boxes
174
175impl<T: LLReader + ?Sized> LLReader for &mut T {
176    fn ll_read(&mut self, path: &[&[u8]]) -> Result<Option<Bytes>, LLError> {
177        (*self).ll_read(path)
178    }
179}
180
181impl<T: LLWriter + ?Sized> LLWriter for &mut T {
182    fn ll_write(&mut self, path: &[&[u8]], data: Bytes) -> Result<LLPath, LLError> {
183        (*self).ll_write(path, data)
184    }
185}
186
187impl<T: LLReader + ?Sized> LLReader for Box<T> {
188    fn ll_read(&mut self, path: &[&[u8]]) -> Result<Option<Bytes>, LLError> {
189        self.as_mut().ll_read(path)
190    }
191}
192
193impl<T: LLWriter + ?Sized> LLWriter for Box<T> {
194    fn ll_write(&mut self, path: &[&[u8]], data: Bytes) -> Result<LLPath, LLError> {
195        self.as_mut().ll_write(path, data)
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use std::collections::HashMap;
203
204    /// A simple in-memory LL store for testing.
205    struct TestLLStore {
206        data: HashMap<Vec<Vec<u8>>, Bytes>,
207    }
208
209    impl TestLLStore {
210        fn new() -> Self {
211            Self {
212                data: HashMap::new(),
213            }
214        }
215    }
216
217    impl LLReader for TestLLStore {
218        fn ll_read(&mut self, path: &[&[u8]]) -> Result<Option<Bytes>, LLError> {
219            let key: Vec<Vec<u8>> = path.iter().map(|c| c.to_vec()).collect();
220            Ok(self.data.get(&key).cloned())
221        }
222    }
223
224    impl LLWriter for TestLLStore {
225        fn ll_write(&mut self, path: &[&[u8]], data: Bytes) -> Result<LLPath, LLError> {
226            let key: Vec<Vec<u8>> = path.iter().map(|c| c.to_vec()).collect();
227            self.data.insert(key, data);
228            Ok(path.iter().map(|c| Bytes::copy_from_slice(c)).collect())
229        }
230    }
231
232    #[test]
233    fn basic_read_write_works() {
234        let mut store = TestLLStore::new();
235
236        // Write some data
237        let path = &[b"users".as_slice(), b"123".as_slice()];
238        let data = Bytes::from_static(b"hello world");
239        store.ll_write(path, data.clone()).unwrap();
240
241        // Read it back
242        let result = store.ll_read(path).unwrap();
243        assert_eq!(result, Some(data));
244
245        // Read non-existent path
246        let result = store.ll_read(&[b"nonexistent"]).unwrap();
247        assert_eq!(result, None);
248    }
249
250    #[test]
251    fn object_safety_works() {
252        let mut store = TestLLStore::new();
253        let boxed: &mut dyn LLStore = &mut store;
254
255        boxed
256            .ll_write(&[b"test"], Bytes::from_static(b"data"))
257            .unwrap();
258        let result = boxed.ll_read(&[b"test"]).unwrap();
259        assert_eq!(result, Some(Bytes::from_static(b"data")));
260    }
261
262    #[test]
263    fn mut_ref_blanket_impl_works() {
264        let mut store = TestLLStore::new();
265        let store_ref: &mut TestLLStore = &mut store;
266
267        store_ref
268            .ll_write(&[b"ref_test"], Bytes::from_static(b"ref_data"))
269            .unwrap();
270        let result = store_ref.ll_read(&[b"ref_test"]).unwrap();
271        assert_eq!(result, Some(Bytes::from_static(b"ref_data")));
272    }
273
274    #[test]
275    fn box_blanket_impl_works() {
276        let store = TestLLStore::new();
277        let mut boxed: Box<TestLLStore> = Box::new(store);
278
279        boxed
280            .ll_write(&[b"box_test"], Bytes::from_static(b"box_data"))
281            .unwrap();
282        let result = boxed.ll_read(&[b"box_test"]).unwrap();
283        assert_eq!(result, Some(Bytes::from_static(b"box_data")));
284    }
285
286    #[test]
287    fn box_dyn_works() {
288        let store = TestLLStore::new();
289        let mut boxed: Box<dyn LLStore> = Box::new(store);
290
291        boxed
292            .ll_write(&[b"dyn_test"], Bytes::from_static(b"dyn_data"))
293            .unwrap();
294        let result = boxed.ll_read(&[b"dyn_test"]).unwrap();
295        assert_eq!(result, Some(Bytes::from_static(b"dyn_data")));
296    }
297}