Skip to main content

structfs_core_store/
traits.rs

1//! Core traits: Reader, Writer, Codec.
2
3use bytes::Bytes;
4
5use crate::{Error, Format, Path, Record, Value};
6
7/// Read records from paths.
8///
9/// This is the semantic read interface. Paths are validated Unicode identifiers,
10/// and the returned Record can be either raw bytes or parsed values.
11///
12/// # Mutability
13///
14/// Both `Reader::read` and `Writer::write` take `&mut self`. This is intentional:
15///
16/// 1. **Stateful stores exist**: Some stores maintain state that changes on read.
17///    For example:
18///    - HTTP broker caches responses after first read
19///    - Filesystem store tracks file position
20///
21/// 2. **Uniformity**: A single trait signature works for all stores. Stores that
22///    don't mutate on read simply ignore the mutability—the compiler optimizes
23///    this away.
24///
25/// 3. **No interior mutability tax**: Stores don't need `Mutex` or `RefCell`
26///    internally just to satisfy the trait. This avoids runtime overhead and
27///    potential deadlocks.
28///
29/// # Concurrent Access
30///
31/// For concurrent access to a store, wrap it explicitly:
32///
33/// ```rust,ignore
34/// use std::sync::{Arc, Mutex};
35///
36/// let store = Arc::new(Mutex::new(MyStore::new()));
37///
38/// // In thread 1:
39/// let mut guard = store.lock().unwrap();
40/// guard.read(&path)?;
41///
42/// // In thread 2:
43/// let mut guard = store.lock().unwrap();
44/// guard.read(&other_path)?;
45/// ```
46///
47/// This makes synchronization explicit at the usage site rather than hidden
48/// in the trait design.
49///
50/// # Object Safety
51///
52/// This trait is object-safe: you can use `Box<dyn Reader>`.
53pub trait Reader: Send + Sync {
54    /// Read a record from a path.
55    ///
56    /// Returns `Ok(Some(record))` if data exists at the path,
57    /// `Ok(None)` if the path doesn't exist,
58    /// or `Err` if an error occurred.
59    fn read(&mut self, from: &Path) -> Result<Option<Record>, Error>;
60
61    /// Enumerate the child names directly under a path.
62    ///
63    /// Returns `Ok(None)` if the path doesn't exist, and `Ok(Some(names))`
64    /// otherwise — an empty vec for leaf values.
65    ///
66    /// The default implementation reads the path and projects children from
67    /// the parsed value: map keys, or indices for arrays. Stores that can
68    /// enumerate more cheaply (or that serve `Record::Raw`) should override
69    /// this.
70    fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
71        let Some(record) = self.read(from)? else {
72            return Ok(None);
73        };
74        match record.as_value() {
75            Some(Value::Map(map)) => Ok(Some(map.keys().cloned().collect())),
76            Some(Value::Array(arr)) => Ok(Some((0..arr.len()).map(|i| i.to_string()).collect())),
77            Some(_) => Ok(Some(Vec::new())),
78            None => Err(Error::store(
79                "reader",
80                "read_children",
81                "cannot enumerate children of a raw record; the store must override read_children",
82            )),
83        }
84    }
85}
86
87/// Write records to paths.
88///
89/// This is the semantic write interface. Paths are validated Unicode identifiers,
90/// and the data can be either raw bytes or parsed values.
91///
92/// See [`Reader`] for discussion of the `&mut self` requirement.
93///
94/// # Object Safety
95///
96/// This trait is object-safe: you can use `Box<dyn Writer>`.
97pub trait Writer: Send + Sync {
98    /// Write a record to a path.
99    ///
100    /// Returns the path where data was written. This may differ from the
101    /// input path—for example, the HTTP broker returns a handle path like
102    /// `/outstanding/0` after queuing a request to the root path.
103    fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error>;
104}
105
106/// Combined read/write at the Core level.
107pub trait Store: Reader + Writer {}
108impl<T: Reader + Writer> Store for T {}
109
110/// Codec for converting between Value and bytes.
111///
112/// Codecs handle the parsing (decode) and serialization (encode) of data.
113/// The Core layer doesn't care about specific formats - that's the codec's job.
114///
115/// # Implementing Custom Codecs
116///
117/// ```rust
118/// use structfs_core_store::{Codec, Value, Format, Error};
119/// use bytes::Bytes;
120///
121/// struct MyProtobufCodec {
122///     // schema, etc.
123/// }
124///
125/// impl Codec for MyProtobufCodec {
126///     fn decode(&self, bytes: &Bytes, format: &Format) -> Result<Value, Error> {
127///         if format != &Format::PROTOBUF {
128///             return Err(Error::UnsupportedFormat(format.clone()));
129///         }
130///         // Parse protobuf bytes into Value...
131///         todo!()
132///     }
133///
134///     fn encode(&self, value: &Value, format: &Format) -> Result<Bytes, Error> {
135///         if format != &Format::PROTOBUF {
136///             return Err(Error::UnsupportedFormat(format.clone()));
137///         }
138///         // Serialize Value to protobuf bytes...
139///         todo!()
140///     }
141///
142///     fn supports(&self, format: &Format) -> bool {
143///         format == &Format::PROTOBUF
144///     }
145/// }
146/// ```
147pub trait Codec: Send + Sync {
148    /// Decode raw bytes into a Value.
149    fn decode(&self, bytes: &Bytes, format: &Format) -> Result<Value, Error>;
150
151    /// Encode a Value into raw bytes.
152    fn encode(&self, value: &Value, format: &Format) -> Result<Bytes, Error>;
153
154    /// Check if this codec supports a format.
155    fn supports(&self, format: &Format) -> bool;
156}
157
158/// A codec that doesn't support any formats.
159///
160/// Useful as a placeholder or for stores that only deal with parsed Values.
161pub struct NoCodec;
162
163impl Codec for NoCodec {
164    fn decode(&self, _bytes: &Bytes, format: &Format) -> Result<Value, Error> {
165        Err(Error::UnsupportedFormat(format.clone()))
166    }
167
168    fn encode(&self, _value: &Value, format: &Format) -> Result<Bytes, Error> {
169        Err(Error::UnsupportedFormat(format.clone()))
170    }
171
172    fn supports(&self, _format: &Format) -> bool {
173        false
174    }
175}
176
177// Blanket implementations for references and boxes
178
179impl<T: Reader + ?Sized> Reader for &mut T {
180    fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
181        (*self).read(from)
182    }
183
184    fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
185        (*self).read_children(from)
186    }
187}
188
189impl<T: Writer + ?Sized> Writer for &mut T {
190    fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
191        (*self).write(to, data)
192    }
193}
194
195impl<T: Reader + ?Sized> Reader for Box<T> {
196    fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
197        self.as_mut().read(from)
198    }
199
200    fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
201        self.as_mut().read_children(from)
202    }
203}
204
205impl<T: Writer + ?Sized> Writer for Box<T> {
206    fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
207        self.as_mut().write(to, data)
208    }
209}
210
211impl<T: Codec + ?Sized> Codec for Box<T> {
212    fn decode(&self, bytes: &Bytes, format: &Format) -> Result<Value, Error> {
213        self.as_ref().decode(bytes, format)
214    }
215
216    fn encode(&self, value: &Value, format: &Format) -> Result<Bytes, Error> {
217        self.as_ref().encode(value, format)
218    }
219
220    fn supports(&self, format: &Format) -> bool {
221        self.as_ref().supports(format)
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228    use std::collections::HashMap;
229
230    /// Simple in-memory store for testing.
231    struct TestStore {
232        data: HashMap<Path, Record>,
233    }
234
235    impl TestStore {
236        fn new() -> Self {
237            Self {
238                data: HashMap::new(),
239            }
240        }
241    }
242
243    impl Reader for TestStore {
244        fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
245            Ok(self.data.get(from).cloned())
246        }
247    }
248
249    impl Writer for TestStore {
250        fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
251            self.data.insert(to.clone(), data);
252            Ok(to.clone())
253        }
254    }
255
256    #[test]
257    fn basic_store_works() {
258        use crate::path;
259
260        let mut store = TestStore::new();
261
262        let path = path!("users/123");
263        let record = Record::parsed(Value::from("Alice"));
264
265        store.write(&path, record.clone()).unwrap();
266
267        let result = store.read(&path).unwrap();
268        assert!(result.is_some());
269    }
270
271    #[test]
272    fn object_safety_works() {
273        use crate::path;
274
275        let mut store = TestStore::new();
276        let boxed: &mut dyn Store = &mut store;
277
278        let path = path!("test");
279        boxed
280            .write(&path, Record::parsed(Value::from("hello")))
281            .unwrap();
282
283        let result = boxed.read(&path).unwrap();
284        assert!(result.is_some());
285    }
286
287    #[test]
288    fn no_codec_decode_fails() {
289        let codec = NoCodec;
290        let bytes = Bytes::from_static(b"hello");
291        let result = codec.decode(&bytes, &Format::JSON);
292        assert!(matches!(result, Err(Error::UnsupportedFormat(_))));
293    }
294
295    #[test]
296    fn no_codec_encode_fails() {
297        let codec = NoCodec;
298        let value = Value::from("test");
299        let result = codec.encode(&value, &Format::JSON);
300        assert!(matches!(result, Err(Error::UnsupportedFormat(_))));
301    }
302
303    #[test]
304    fn no_codec_supports_nothing() {
305        let codec = NoCodec;
306        assert!(!codec.supports(&Format::JSON));
307        assert!(!codec.supports(&Format::PROTOBUF));
308        assert!(!codec.supports(&Format::OCTET_STREAM));
309    }
310
311    #[test]
312    fn ref_mut_reader_works() {
313        use crate::path;
314
315        let mut store = TestStore::new();
316        let path = path!("test");
317        store
318            .write(&path, Record::parsed(Value::from("value")))
319            .unwrap();
320
321        // Use &mut reference as Reader
322        let store_ref: &mut TestStore = &mut store;
323        let result = store_ref.read(&path).unwrap();
324        assert!(result.is_some());
325    }
326
327    #[test]
328    fn ref_mut_writer_works() {
329        use crate::path;
330
331        let mut store = TestStore::new();
332
333        // Use &mut reference as Writer
334        let store_ref: &mut TestStore = &mut store;
335        let path = path!("test");
336        let result = store_ref.write(&path, Record::parsed(Value::from("data")));
337        assert!(result.is_ok());
338
339        // Verify it was written
340        let read_result = store.read(&path).unwrap();
341        assert!(read_result.is_some());
342    }
343
344    #[test]
345    fn boxed_reader_works() {
346        use crate::path;
347
348        let mut store = TestStore::new();
349        let path = path!("boxed_test");
350        store
351            .write(&path, Record::parsed(Value::from("boxed_value")))
352            .unwrap();
353
354        // Use Box as Reader
355        let mut boxed: Box<TestStore> = Box::new(store);
356        let result = boxed.read(&path).unwrap();
357        assert!(result.is_some());
358    }
359
360    #[test]
361    fn boxed_writer_works() {
362        use crate::path;
363
364        let store = TestStore::new();
365        let mut boxed: Box<TestStore> = Box::new(store);
366
367        let path = path!("boxed_write");
368        let result = boxed.write(&path, Record::parsed(Value::from("data")));
369        assert!(result.is_ok());
370
371        // Verify it was written
372        let read_result = boxed.read(&path).unwrap();
373        assert!(read_result.is_some());
374    }
375
376    #[test]
377    fn boxed_codec_works() {
378        // Create a simple test codec
379        struct TestCodec;
380
381        impl Codec for TestCodec {
382            fn decode(&self, bytes: &Bytes, _format: &Format) -> Result<Value, Error> {
383                // Simple: treat bytes as UTF-8 string
384                let s = String::from_utf8_lossy(bytes);
385                Ok(Value::String(s.to_string()))
386            }
387
388            fn encode(&self, value: &Value, _format: &Format) -> Result<Bytes, Error> {
389                match value {
390                    Value::String(s) => Ok(Bytes::from(s.clone())),
391                    _ => Err(Error::encode(Format::OCTET_STREAM, "only strings")),
392                }
393            }
394
395            fn supports(&self, format: &Format) -> bool {
396                format == &Format::OCTET_STREAM
397            }
398        }
399
400        let boxed: Box<dyn Codec> = Box::new(TestCodec);
401
402        // Test supports
403        assert!(boxed.supports(&Format::OCTET_STREAM));
404        assert!(!boxed.supports(&Format::JSON));
405
406        // Test decode
407        let decoded = boxed
408            .decode(&Bytes::from_static(b"hello"), &Format::OCTET_STREAM)
409            .unwrap();
410        assert_eq!(decoded, Value::String("hello".to_string()));
411
412        // Test encode
413        let encoded = boxed
414            .encode(&Value::String("world".to_string()), &Format::OCTET_STREAM)
415            .unwrap();
416        assert_eq!(encoded.as_ref(), b"world");
417    }
418
419    #[test]
420    fn store_trait_auto_impl() {
421        // Verify that anything implementing Reader + Writer auto-implements Store
422        fn requires_store<S: Store>(_s: &mut S) {}
423
424        let mut store = TestStore::new();
425        requires_store(&mut store); // This compiles because TestStore: Reader + Writer
426    }
427
428    #[test]
429    fn read_missing_returns_none() {
430        use crate::path;
431
432        let mut store = TestStore::new();
433        let result = store.read(&path!("nonexistent")).unwrap();
434        assert!(result.is_none());
435    }
436
437    #[test]
438    fn read_children_default_impl() {
439        use crate::path;
440        use std::collections::BTreeMap;
441
442        let mut store = TestStore::new();
443
444        // Map value: children are the keys
445        let mut map = BTreeMap::new();
446        map.insert("alice".to_string(), Value::from(1i64));
447        map.insert("bob".to_string(), Value::from(2i64));
448        store
449            .write(&path!("users"), Record::parsed(Value::Map(map)))
450            .unwrap();
451        assert_eq!(
452            store.read_children(&path!("users")).unwrap(),
453            Some(vec!["alice".to_string(), "bob".to_string()])
454        );
455
456        // Array value: children are indices
457        store
458            .write(
459                &path!("items"),
460                Record::parsed(Value::Array(vec![Value::from("a"), Value::from("b")])),
461            )
462            .unwrap();
463        assert_eq!(
464            store.read_children(&path!("items")).unwrap(),
465            Some(vec!["0".to_string(), "1".to_string()])
466        );
467
468        // Leaf value: empty children
469        store
470            .write(&path!("leaf"), Record::parsed(Value::from("scalar")))
471            .unwrap();
472        assert_eq!(store.read_children(&path!("leaf")).unwrap(), Some(vec![]));
473
474        // Missing path: None
475        assert_eq!(store.read_children(&path!("missing")).unwrap(), None);
476    }
477
478    #[test]
479    fn read_children_raw_record_errors() {
480        use crate::path;
481
482        let mut store = TestStore::new();
483        store
484            .write(
485                &path!("raw"),
486                Record::raw(Bytes::from_static(b"{}"), Format::JSON),
487            )
488            .unwrap();
489        assert!(store.read_children(&path!("raw")).is_err());
490    }
491
492    #[test]
493    fn read_children_delegates_through_wrappers() {
494        use crate::path;
495
496        /// Store that overrides read_children without storing map values.
497        struct ListingStore;
498
499        impl Reader for ListingStore {
500            fn read(&mut self, _from: &Path) -> Result<Option<Record>, Error> {
501                Ok(None)
502            }
503
504            fn read_children(&mut self, _from: &Path) -> Result<Option<Vec<String>>, Error> {
505                Ok(Some(vec!["custom".to_string()]))
506            }
507        }
508
509        let mut store = ListingStore;
510        let by_ref: &mut dyn Reader = &mut store;
511        assert_eq!(
512            by_ref.read_children(&path!("x")).unwrap(),
513            Some(vec!["custom".to_string()])
514        );
515
516        let mut boxed: Box<dyn Reader> = Box::new(ListingStore);
517        assert_eq!(
518            boxed.read_children(&path!("x")).unwrap(),
519            Some(vec!["custom".to_string()])
520        );
521    }
522}