Skip to main content

structfs_core_store/
bridge.rs

1//! Bridges between LL and Core layers.
2//!
3//! These adapters allow using LL stores from the Core layer and vice versa.
4//!
5//! # LL → Core Bridge
6//!
7//! Wrap an `LLStore` to get a Core `Store`:
8//!
9//! ```rust,ignore
10//! let ll_store = SomeLLStore::new();
11//! let core_store = LLToCore::new(ll_store, Format::JSON);
12//! // Now use core_store as a Reader/Writer
13//! ```
14//!
15//! # Core → LL Bridge
16//!
17//! Wrap a Core `Store` to get an `LLStore`:
18//!
19//! ```rust,ignore
20//! let core_store = SomeCoreStore::new();
21//! let ll_store = CoreToLL::new(core_store, JsonCodec, Format::JSON);
22//! // Now use ll_store as an LLReader/LLWriter
23//! ```
24
25use bytes::Bytes;
26use structfs_ll_store::{LLError, LLPath, LLReader, LLWriter};
27
28use crate::{Codec, Error, Format, Path, PathError, Reader, Record, Writer};
29
30/// Adapts an LL store to the Core Store interface.
31///
32/// This bridge:
33/// - Converts `&[&[u8]]` paths to validated `Path`
34/// - Wraps returned bytes as `Record::Raw` with a format hint
35/// - Serializes `Record` to bytes for writes
36pub struct LLToCore<T, C> {
37    inner: T,
38    codec: C,
39    /// Format hint for data read from LL layer.
40    read_format: Format,
41    /// Format to use when serializing for LL writes.
42    write_format: Format,
43}
44
45impl<T, C> LLToCore<T, C> {
46    /// Create a new bridge with the same format for reads and writes.
47    pub fn new(inner: T, codec: C, format: Format) -> Self {
48        Self {
49            inner,
50            codec,
51            read_format: format.clone(),
52            write_format: format,
53        }
54    }
55
56    /// Create a new bridge with different formats for reads and writes.
57    pub fn with_formats(inner: T, codec: C, read_format: Format, write_format: Format) -> Self {
58        Self {
59            inner,
60            codec,
61            read_format,
62            write_format,
63        }
64    }
65
66    /// Get a reference to the inner LL store.
67    pub fn inner(&self) -> &T {
68        &self.inner
69    }
70
71    /// Get a mutable reference to the inner LL store.
72    pub fn inner_mut(&mut self) -> &mut T {
73        &mut self.inner
74    }
75
76    /// Unwrap, returning the inner LL store.
77    pub fn into_inner(self) -> T {
78        self.inner
79    }
80}
81
82impl<T: LLReader, C: Send + Sync> Reader for LLToCore<T, C> {
83    fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
84        // Borrow the validated byte components (free widening, no copy).
85        let components: Vec<&[u8]> = from.as_ll().as_byte_refs();
86
87        // Read via LL
88        let bytes = match self.inner.ll_read(&components) {
89            Ok(Some(b)) => b,
90            Ok(None) => return Ok(None),
91            Err(e) => return Err(Error::Ll(e)),
92        };
93
94        // Wrap as Raw record with our format hint
95        Ok(Some(Record::raw(bytes, self.read_format.clone())))
96    }
97}
98
99impl<T: LLWriter, C: Codec + Send + Sync> Writer for LLToCore<T, C> {
100    fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
101        // Get bytes from Record (serialize if Parsed)
102        let bytes = data.into_bytes(&self.codec, &self.write_format)?;
103
104        // Borrow the validated byte components (free widening, no copy).
105        let components: Vec<&[u8]> = to.as_ll().as_byte_refs();
106
107        // Write via LL
108        let result_path = self.inner.ll_write(&components, bytes).map_err(Error::Ll)?;
109
110        // Convert result back to Path
111        path_from_ll(&result_path)
112    }
113}
114
115/// Adapts a Core store to the LL Store interface.
116///
117/// This bridge:
118/// - Converts `&[&[u8]]` paths to validated `Path`
119/// - Parses/serializes data as needed
120/// - Returns bytes in the configured format
121pub struct CoreToLL<T, C> {
122    inner: T,
123    codec: C,
124    format: Format,
125}
126
127impl<T, C> CoreToLL<T, C> {
128    /// Create a new bridge.
129    pub fn new(inner: T, codec: C, format: Format) -> Self {
130        Self {
131            inner,
132            codec,
133            format,
134        }
135    }
136
137    /// Get a reference to the inner Core store.
138    pub fn inner(&self) -> &T {
139        &self.inner
140    }
141
142    /// Get a mutable reference to the inner Core store.
143    pub fn inner_mut(&mut self) -> &mut T {
144        &mut self.inner
145    }
146
147    /// Unwrap, returning the inner Core store.
148    pub fn into_inner(self) -> T {
149        self.inner
150    }
151}
152
153impl<T: Reader, C: Codec + Send + Sync> LLReader for CoreToLL<T, C> {
154    fn ll_read(&mut self, path: &[&[u8]]) -> Result<Option<Bytes>, LLError> {
155        // Convert &[&[u8]] to Path
156        let path = path_from_bytes(path).map_err(|e| LLError::Protocol {
157            code: 1,
158            detail: Bytes::copy_from_slice(e.to_string().as_bytes()),
159        })?;
160
161        // Read via Core
162        let record = match self.inner.read(&path) {
163            Ok(Some(r)) => r,
164            Ok(None) => return Ok(None),
165            Err(e) => {
166                return Err(LLError::Protocol {
167                    code: 2,
168                    detail: Bytes::copy_from_slice(e.to_string().as_bytes()),
169                })
170            }
171        };
172
173        // Convert to bytes
174        let bytes =
175            record
176                .into_bytes(&self.codec, &self.format)
177                .map_err(|e| LLError::Protocol {
178                    code: 3,
179                    detail: Bytes::copy_from_slice(e.to_string().as_bytes()),
180                })?;
181
182        Ok(Some(bytes))
183    }
184}
185
186impl<T: Writer, C: Send + Sync> LLWriter for CoreToLL<T, C> {
187    fn ll_write(&mut self, path: &[&[u8]], data: Bytes) -> Result<LLPath, LLError> {
188        // Convert path
189        let path = path_from_bytes(path).map_err(|e| LLError::Protocol {
190            code: 1,
191            detail: Bytes::copy_from_slice(e.to_string().as_bytes()),
192        })?;
193
194        // Wrap data as Raw record
195        let record = Record::raw(data, self.format.clone());
196
197        // Write via Core
198        let result_path = self
199            .inner
200            .write(&path, record)
201            .map_err(|e| LLError::Protocol {
202                code: 2,
203                detail: Bytes::copy_from_slice(e.to_string().as_bytes()),
204            })?;
205
206        // Widen the validated result path to LL (free — no component copy).
207        Ok(result_path.into_ll())
208    }
209}
210
211/// Convert LL path components to Core Path.
212pub(crate) fn path_from_bytes(components: &[&[u8]]) -> Result<Path, PathError> {
213    let mut strings = Vec::with_capacity(components.len());
214    for (i, bytes) in components.iter().enumerate() {
215        let s = std::str::from_utf8(bytes).map_err(|_| PathError::InvalidComponent {
216            component: format!("{:?}", bytes),
217            position: i,
218            message: "not valid UTF-8".to_string(),
219        })?;
220        strings.push(s.to_string());
221    }
222    Path::try_from_components(strings)
223}
224
225/// Convert LL path (owned) to Core Path.
226pub(crate) fn path_from_ll(components: &[Bytes]) -> Result<Path, Error> {
227    let refs: Vec<&[u8]> = components.iter().map(|b| b.as_ref()).collect();
228    path_from_bytes(&refs).map_err(Error::Path)
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234    use crate::{path, NoCodec};
235    use std::collections::HashMap;
236
237    /// Simple in-memory LL store for testing.
238    struct TestLLStore {
239        data: HashMap<Vec<Vec<u8>>, Bytes>,
240    }
241
242    impl TestLLStore {
243        fn new() -> Self {
244            Self {
245                data: HashMap::new(),
246            }
247        }
248    }
249
250    impl LLReader for TestLLStore {
251        fn ll_read(&mut self, path: &[&[u8]]) -> Result<Option<Bytes>, LLError> {
252            let key: Vec<Vec<u8>> = path.iter().map(|c| c.to_vec()).collect();
253            Ok(self.data.get(&key).cloned())
254        }
255    }
256
257    impl LLWriter for TestLLStore {
258        fn ll_write(&mut self, path: &[&[u8]], data: Bytes) -> Result<LLPath, LLError> {
259            let key: Vec<Vec<u8>> = path.iter().map(|c| c.to_vec()).collect();
260            self.data.insert(key, data);
261            Ok(path.iter().map(|c| Bytes::copy_from_slice(c)).collect())
262        }
263    }
264
265    /// Simple in-memory Core store for testing.
266    struct TestCoreStore {
267        data: HashMap<Path, Record>,
268    }
269
270    impl TestCoreStore {
271        fn new() -> Self {
272            Self {
273                data: HashMap::new(),
274            }
275        }
276    }
277
278    impl Reader for TestCoreStore {
279        fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
280            Ok(self.data.get(from).cloned())
281        }
282    }
283
284    impl Writer for TestCoreStore {
285        fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
286            self.data.insert(to.clone(), data);
287            Ok(to.clone())
288        }
289    }
290
291    #[test]
292    fn ll_to_core_read() {
293        let mut ll = TestLLStore::new();
294        ll.data.insert(
295            vec![b"users".to_vec(), b"123".to_vec()],
296            Bytes::from_static(b"hello"),
297        );
298
299        let mut bridge = LLToCore::new(ll, NoCodec, Format::OCTET_STREAM);
300
301        let result = bridge.read(&path!("users/123")).unwrap();
302        assert!(result.is_some());
303        assert_eq!(
304            result.unwrap().as_bytes(),
305            Some(&Bytes::from_static(b"hello"))
306        );
307    }
308
309    #[test]
310    fn ll_to_core_write() {
311        let ll = TestLLStore::new();
312        let mut bridge = LLToCore::new(ll, NoCodec, Format::OCTET_STREAM);
313
314        let record = Record::raw(Bytes::from_static(b"data"), Format::OCTET_STREAM);
315        bridge.write(&path!("test/path"), record).unwrap();
316
317        // Verify it was written
318        let key = vec![b"test".to_vec(), b"path".to_vec()];
319        assert!(bridge.inner().data.contains_key(&key));
320    }
321
322    #[test]
323    fn core_to_ll_read() {
324        let mut core = TestCoreStore::new();
325        core.data.insert(
326            path!("users/123"),
327            Record::raw(Bytes::from_static(b"hello"), Format::OCTET_STREAM),
328        );
329
330        let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
331
332        let result = bridge.ll_read(&[b"users", b"123"]).unwrap();
333        assert_eq!(result, Some(Bytes::from_static(b"hello")));
334    }
335
336    #[test]
337    fn core_to_ll_write() {
338        let core = TestCoreStore::new();
339        let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
340
341        bridge
342            .ll_write(&[b"test", b"path"], Bytes::from_static(b"data"))
343            .unwrap();
344
345        // Verify it was written
346        assert!(bridge.inner().data.contains_key(&path!("test/path")));
347    }
348
349    #[test]
350    fn invalid_utf8_path_rejected() {
351        let core = TestCoreStore::new();
352        let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
353
354        // Invalid UTF-8 sequence
355        let result = bridge.ll_read(&[&[0xFF, 0xFE]]);
356        assert!(matches!(result, Err(LLError::Protocol { .. })));
357    }
358
359    #[test]
360    fn ll_to_core_with_formats() {
361        let ll = TestLLStore::new();
362        let bridge = LLToCore::with_formats(ll, NoCodec, Format::JSON, Format::OCTET_STREAM);
363        assert_eq!(bridge.read_format, Format::JSON);
364        assert_eq!(bridge.write_format, Format::OCTET_STREAM);
365    }
366
367    #[test]
368    fn ll_to_core_inner_methods() {
369        let ll = TestLLStore::new();
370        let mut bridge = LLToCore::new(ll, NoCodec, Format::OCTET_STREAM);
371
372        // Test inner()
373        assert!(bridge.inner().data.is_empty());
374
375        // Test inner_mut()
376        bridge
377            .inner_mut()
378            .data
379            .insert(vec![b"key".to_vec()], Bytes::from_static(b"value"));
380        assert!(!bridge.inner().data.is_empty());
381
382        // Test into_inner()
383        let ll = bridge.into_inner();
384        assert!(!ll.data.is_empty());
385    }
386
387    #[test]
388    fn core_to_ll_inner_methods() {
389        let core = TestCoreStore::new();
390        let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
391
392        // Test inner()
393        assert!(bridge.inner().data.is_empty());
394
395        // Test inner_mut()
396        bridge.inner_mut().data.insert(
397            path!("key"),
398            Record::raw(Bytes::from_static(b"value"), Format::OCTET_STREAM),
399        );
400        assert!(!bridge.inner().data.is_empty());
401
402        // Test into_inner()
403        let core = bridge.into_inner();
404        assert!(!core.data.is_empty());
405    }
406
407    #[test]
408    fn ll_to_core_read_none() {
409        let ll = TestLLStore::new();
410        let mut bridge = LLToCore::new(ll, NoCodec, Format::OCTET_STREAM);
411
412        let result = bridge.read(&path!("nonexistent")).unwrap();
413        assert!(result.is_none());
414    }
415
416    #[test]
417    fn core_to_ll_read_none() {
418        let core = TestCoreStore::new();
419        let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
420
421        let result = bridge.ll_read(&[b"nonexistent"]).unwrap();
422        assert!(result.is_none());
423    }
424
425    #[test]
426    fn core_to_ll_write_invalid_utf8() {
427        let core = TestCoreStore::new();
428        let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
429
430        // Invalid UTF-8 sequence
431        let result = bridge.ll_write(&[&[0xFF, 0xFE]], Bytes::from_static(b"data"));
432        assert!(matches!(result, Err(LLError::Protocol { code: 1, .. })));
433    }
434
435    #[test]
436    fn path_from_bytes_empty() {
437        let result = path_from_bytes(&[]).unwrap();
438        assert!(result.is_empty());
439    }
440
441    #[test]
442    fn path_from_bytes_single_component() {
443        let result = path_from_bytes(&[b"users"]).unwrap();
444        assert_eq!(result.to_string(), "users");
445    }
446
447    #[test]
448    fn path_from_bytes_multiple_components() {
449        let result = path_from_bytes(&[b"users", b"123", b"profile"]).unwrap();
450        assert_eq!(result.to_string(), "users/123/profile");
451    }
452
453    #[test]
454    fn path_from_ll_works() {
455        let ll_path = vec![Bytes::from_static(b"a"), Bytes::from_static(b"b")];
456        let result = path_from_ll(&ll_path).unwrap();
457        assert_eq!(result.to_string(), "a/b");
458    }
459
460    #[test]
461    fn path_from_ll_invalid_utf8() {
462        let ll_path = vec![Bytes::from_static(&[0xFF, 0xFE])];
463        let result = path_from_ll(&ll_path);
464        assert!(result.is_err());
465    }
466
467    /// Store that always returns an error on read.
468    struct ErrorCoreStore;
469
470    impl Reader for ErrorCoreStore {
471        fn read(&mut self, _from: &Path) -> Result<Option<Record>, Error> {
472            Err(Error::store("test", "read", "read error"))
473        }
474    }
475
476    impl Writer for ErrorCoreStore {
477        fn write(&mut self, _to: &Path, _data: Record) -> Result<Path, Error> {
478            Err(Error::store("test", "write", "write error"))
479        }
480    }
481
482    #[test]
483    fn core_to_ll_read_error() {
484        let core = ErrorCoreStore;
485        let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
486
487        let result = bridge.ll_read(&[b"any"]);
488        assert!(matches!(result, Err(LLError::Protocol { code: 2, .. })));
489    }
490
491    #[test]
492    fn core_to_ll_write_error() {
493        let core = ErrorCoreStore;
494        let mut bridge = CoreToLL::new(core, NoCodec, Format::OCTET_STREAM);
495
496        let result = bridge.ll_write(&[b"any"], Bytes::from_static(b"data"));
497        assert!(matches!(result, Err(LLError::Protocol { code: 2, .. })));
498    }
499
500    /// LL store that always returns an error.
501    struct ErrorLLStore;
502
503    impl LLReader for ErrorLLStore {
504        fn ll_read(&mut self, _path: &[&[u8]]) -> Result<Option<Bytes>, LLError> {
505            Err(LLError::Protocol {
506                code: 99,
507                detail: Bytes::from_static(b"ll error"),
508            })
509        }
510    }
511
512    impl LLWriter for ErrorLLStore {
513        fn ll_write(&mut self, _path: &[&[u8]], _data: Bytes) -> Result<LLPath, LLError> {
514            Err(LLError::Protocol {
515                code: 99,
516                detail: Bytes::from_static(b"ll write error"),
517            })
518        }
519    }
520
521    #[test]
522    fn ll_to_core_read_error() {
523        let ll = ErrorLLStore;
524        let mut bridge = LLToCore::new(ll, NoCodec, Format::OCTET_STREAM);
525
526        let result = bridge.read(&path!("any"));
527        assert!(result.is_err());
528        assert!(result.unwrap_err().to_string().contains("ll error"));
529    }
530
531    #[test]
532    fn ll_to_core_write_error() {
533        let ll = ErrorLLStore;
534        let mut bridge = LLToCore::new(ll, NoCodec, Format::OCTET_STREAM);
535
536        let result = bridge.write(
537            &path!("any"),
538            Record::raw(Bytes::from_static(b"data"), Format::OCTET_STREAM),
539        );
540        assert!(result.is_err());
541    }
542}