structfs-core-store 0.2.0

Core StructFS store traits - Record, Value, Path, Format
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
//! Core traits: Reader, Writer, Codec.

use bytes::Bytes;

use crate::{Error, Format, Path, Record, Value};

/// Read records from paths.
///
/// This is the semantic read interface. Paths are validated Unicode identifiers,
/// and the returned Record can be either raw bytes or parsed values.
///
/// # Mutability
///
/// Both `Reader::read` and `Writer::write` take `&mut self`. This is intentional:
///
/// 1. **Stateful stores exist**: Some stores maintain state that changes on read.
///    For example:
///    - HTTP broker caches responses after first read
///    - Filesystem store tracks file position
///
/// 2. **Uniformity**: A single trait signature works for all stores. Stores that
///    don't mutate on read simply ignore the mutability—the compiler optimizes
///    this away.
///
/// 3. **No interior mutability tax**: Stores don't need `Mutex` or `RefCell`
///    internally just to satisfy the trait. This avoids runtime overhead and
///    potential deadlocks.
///
/// # Concurrent Access
///
/// For concurrent access to a store, wrap it explicitly:
///
/// ```rust,ignore
/// use std::sync::{Arc, Mutex};
///
/// let store = Arc::new(Mutex::new(MyStore::new()));
///
/// // In thread 1:
/// let mut guard = store.lock().unwrap();
/// guard.read(&path)?;
///
/// // In thread 2:
/// let mut guard = store.lock().unwrap();
/// guard.read(&other_path)?;
/// ```
///
/// This makes synchronization explicit at the usage site rather than hidden
/// in the trait design.
///
/// # Object Safety
///
/// This trait is object-safe: you can use `Box<dyn Reader>`.
pub trait Reader: Send + Sync {
    /// Read a record from a path.
    ///
    /// Returns `Ok(Some(record))` if data exists at the path,
    /// `Ok(None)` if the path doesn't exist,
    /// or `Err` if an error occurred.
    fn read(&mut self, from: &Path) -> Result<Option<Record>, Error>;

    /// Enumerate the child names directly under a path.
    ///
    /// Returns `Ok(None)` if the path doesn't exist, and `Ok(Some(names))`
    /// otherwise — an empty vec for leaf values.
    ///
    /// The default implementation reads the path and projects children from
    /// the parsed value: map keys, or indices for arrays. Stores that can
    /// enumerate more cheaply (or that serve `Record::Raw`) should override
    /// this.
    fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
        let Some(record) = self.read(from)? else {
            return Ok(None);
        };
        match record.as_value() {
            Some(Value::Map(map)) => Ok(Some(map.keys().cloned().collect())),
            Some(Value::Array(arr)) => Ok(Some((0..arr.len()).map(|i| i.to_string()).collect())),
            Some(_) => Ok(Some(Vec::new())),
            None => Err(Error::store(
                "reader",
                "read_children",
                "cannot enumerate children of a raw record; the store must override read_children",
            )),
        }
    }
}

/// Write records to paths.
///
/// This is the semantic write interface. Paths are validated Unicode identifiers,
/// and the data can be either raw bytes or parsed values.
///
/// See [`Reader`] for discussion of the `&mut self` requirement.
///
/// # Object Safety
///
/// This trait is object-safe: you can use `Box<dyn Writer>`.
pub trait Writer: Send + Sync {
    /// Write a record to a path.
    ///
    /// Returns the path where data was written. This may differ from the
    /// input path—for example, the HTTP broker returns a handle path like
    /// `/outstanding/0` after queuing a request to the root path.
    fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error>;
}

/// Combined read/write at the Core level.
pub trait Store: Reader + Writer {}
impl<T: Reader + Writer> Store for T {}

/// Codec for converting between Value and bytes.
///
/// Codecs handle the parsing (decode) and serialization (encode) of data.
/// The Core layer doesn't care about specific formats - that's the codec's job.
///
/// # Implementing Custom Codecs
///
/// ```rust
/// use structfs_core_store::{Codec, Value, Format, Error};
/// use bytes::Bytes;
///
/// struct MyProtobufCodec {
///     // schema, etc.
/// }
///
/// impl Codec for MyProtobufCodec {
///     fn decode(&self, bytes: &Bytes, format: &Format) -> Result<Value, Error> {
///         if format != &Format::PROTOBUF {
///             return Err(Error::UnsupportedFormat(format.clone()));
///         }
///         // Parse protobuf bytes into Value...
///         todo!()
///     }
///
///     fn encode(&self, value: &Value, format: &Format) -> Result<Bytes, Error> {
///         if format != &Format::PROTOBUF {
///             return Err(Error::UnsupportedFormat(format.clone()));
///         }
///         // Serialize Value to protobuf bytes...
///         todo!()
///     }
///
///     fn supports(&self, format: &Format) -> bool {
///         format == &Format::PROTOBUF
///     }
/// }
/// ```
pub trait Codec: Send + Sync {
    /// Decode raw bytes into a Value.
    fn decode(&self, bytes: &Bytes, format: &Format) -> Result<Value, Error>;

    /// Encode a Value into raw bytes.
    fn encode(&self, value: &Value, format: &Format) -> Result<Bytes, Error>;

    /// Check if this codec supports a format.
    fn supports(&self, format: &Format) -> bool;
}

/// A codec that doesn't support any formats.
///
/// Useful as a placeholder or for stores that only deal with parsed Values.
pub struct NoCodec;

impl Codec for NoCodec {
    fn decode(&self, _bytes: &Bytes, format: &Format) -> Result<Value, Error> {
        Err(Error::UnsupportedFormat(format.clone()))
    }

    fn encode(&self, _value: &Value, format: &Format) -> Result<Bytes, Error> {
        Err(Error::UnsupportedFormat(format.clone()))
    }

    fn supports(&self, _format: &Format) -> bool {
        false
    }
}

// Blanket implementations for references and boxes

impl<T: Reader + ?Sized> Reader for &mut T {
    fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
        (*self).read(from)
    }

    fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
        (*self).read_children(from)
    }
}

impl<T: Writer + ?Sized> Writer for &mut T {
    fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
        (*self).write(to, data)
    }
}

impl<T: Reader + ?Sized> Reader for Box<T> {
    fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
        self.as_mut().read(from)
    }

    fn read_children(&mut self, from: &Path) -> Result<Option<Vec<String>>, Error> {
        self.as_mut().read_children(from)
    }
}

impl<T: Writer + ?Sized> Writer for Box<T> {
    fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
        self.as_mut().write(to, data)
    }
}

impl<T: Codec + ?Sized> Codec for Box<T> {
    fn decode(&self, bytes: &Bytes, format: &Format) -> Result<Value, Error> {
        self.as_ref().decode(bytes, format)
    }

    fn encode(&self, value: &Value, format: &Format) -> Result<Bytes, Error> {
        self.as_ref().encode(value, format)
    }

    fn supports(&self, format: &Format) -> bool {
        self.as_ref().supports(format)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;

    /// Simple in-memory store for testing.
    struct TestStore {
        data: HashMap<Path, Record>,
    }

    impl TestStore {
        fn new() -> Self {
            Self {
                data: HashMap::new(),
            }
        }
    }

    impl Reader for TestStore {
        fn read(&mut self, from: &Path) -> Result<Option<Record>, Error> {
            Ok(self.data.get(from).cloned())
        }
    }

    impl Writer for TestStore {
        fn write(&mut self, to: &Path, data: Record) -> Result<Path, Error> {
            self.data.insert(to.clone(), data);
            Ok(to.clone())
        }
    }

    #[test]
    fn basic_store_works() {
        use crate::path;

        let mut store = TestStore::new();

        let path = path!("users/123");
        let record = Record::parsed(Value::from("Alice"));

        store.write(&path, record.clone()).unwrap();

        let result = store.read(&path).unwrap();
        assert!(result.is_some());
    }

    #[test]
    fn object_safety_works() {
        use crate::path;

        let mut store = TestStore::new();
        let boxed: &mut dyn Store = &mut store;

        let path = path!("test");
        boxed
            .write(&path, Record::parsed(Value::from("hello")))
            .unwrap();

        let result = boxed.read(&path).unwrap();
        assert!(result.is_some());
    }

    #[test]
    fn no_codec_decode_fails() {
        let codec = NoCodec;
        let bytes = Bytes::from_static(b"hello");
        let result = codec.decode(&bytes, &Format::JSON);
        assert!(matches!(result, Err(Error::UnsupportedFormat(_))));
    }

    #[test]
    fn no_codec_encode_fails() {
        let codec = NoCodec;
        let value = Value::from("test");
        let result = codec.encode(&value, &Format::JSON);
        assert!(matches!(result, Err(Error::UnsupportedFormat(_))));
    }

    #[test]
    fn no_codec_supports_nothing() {
        let codec = NoCodec;
        assert!(!codec.supports(&Format::JSON));
        assert!(!codec.supports(&Format::PROTOBUF));
        assert!(!codec.supports(&Format::OCTET_STREAM));
    }

    #[test]
    fn ref_mut_reader_works() {
        use crate::path;

        let mut store = TestStore::new();
        let path = path!("test");
        store
            .write(&path, Record::parsed(Value::from("value")))
            .unwrap();

        // Use &mut reference as Reader
        let store_ref: &mut TestStore = &mut store;
        let result = store_ref.read(&path).unwrap();
        assert!(result.is_some());
    }

    #[test]
    fn ref_mut_writer_works() {
        use crate::path;

        let mut store = TestStore::new();

        // Use &mut reference as Writer
        let store_ref: &mut TestStore = &mut store;
        let path = path!("test");
        let result = store_ref.write(&path, Record::parsed(Value::from("data")));
        assert!(result.is_ok());

        // Verify it was written
        let read_result = store.read(&path).unwrap();
        assert!(read_result.is_some());
    }

    #[test]
    fn boxed_reader_works() {
        use crate::path;

        let mut store = TestStore::new();
        let path = path!("boxed_test");
        store
            .write(&path, Record::parsed(Value::from("boxed_value")))
            .unwrap();

        // Use Box as Reader
        let mut boxed: Box<TestStore> = Box::new(store);
        let result = boxed.read(&path).unwrap();
        assert!(result.is_some());
    }

    #[test]
    fn boxed_writer_works() {
        use crate::path;

        let store = TestStore::new();
        let mut boxed: Box<TestStore> = Box::new(store);

        let path = path!("boxed_write");
        let result = boxed.write(&path, Record::parsed(Value::from("data")));
        assert!(result.is_ok());

        // Verify it was written
        let read_result = boxed.read(&path).unwrap();
        assert!(read_result.is_some());
    }

    #[test]
    fn boxed_codec_works() {
        // Create a simple test codec
        struct TestCodec;

        impl Codec for TestCodec {
            fn decode(&self, bytes: &Bytes, _format: &Format) -> Result<Value, Error> {
                // Simple: treat bytes as UTF-8 string
                let s = String::from_utf8_lossy(bytes);
                Ok(Value::String(s.to_string()))
            }

            fn encode(&self, value: &Value, _format: &Format) -> Result<Bytes, Error> {
                match value {
                    Value::String(s) => Ok(Bytes::from(s.clone())),
                    _ => Err(Error::encode(Format::OCTET_STREAM, "only strings")),
                }
            }

            fn supports(&self, format: &Format) -> bool {
                format == &Format::OCTET_STREAM
            }
        }

        let boxed: Box<dyn Codec> = Box::new(TestCodec);

        // Test supports
        assert!(boxed.supports(&Format::OCTET_STREAM));
        assert!(!boxed.supports(&Format::JSON));

        // Test decode
        let decoded = boxed
            .decode(&Bytes::from_static(b"hello"), &Format::OCTET_STREAM)
            .unwrap();
        assert_eq!(decoded, Value::String("hello".to_string()));

        // Test encode
        let encoded = boxed
            .encode(&Value::String("world".to_string()), &Format::OCTET_STREAM)
            .unwrap();
        assert_eq!(encoded.as_ref(), b"world");
    }

    #[test]
    fn store_trait_auto_impl() {
        // Verify that anything implementing Reader + Writer auto-implements Store
        fn requires_store<S: Store>(_s: &mut S) {}

        let mut store = TestStore::new();
        requires_store(&mut store); // This compiles because TestStore: Reader + Writer
    }

    #[test]
    fn read_missing_returns_none() {
        use crate::path;

        let mut store = TestStore::new();
        let result = store.read(&path!("nonexistent")).unwrap();
        assert!(result.is_none());
    }

    #[test]
    fn read_children_default_impl() {
        use crate::path;
        use std::collections::BTreeMap;

        let mut store = TestStore::new();

        // Map value: children are the keys
        let mut map = BTreeMap::new();
        map.insert("alice".to_string(), Value::from(1i64));
        map.insert("bob".to_string(), Value::from(2i64));
        store
            .write(&path!("users"), Record::parsed(Value::Map(map)))
            .unwrap();
        assert_eq!(
            store.read_children(&path!("users")).unwrap(),
            Some(vec!["alice".to_string(), "bob".to_string()])
        );

        // Array value: children are indices
        store
            .write(
                &path!("items"),
                Record::parsed(Value::Array(vec![Value::from("a"), Value::from("b")])),
            )
            .unwrap();
        assert_eq!(
            store.read_children(&path!("items")).unwrap(),
            Some(vec!["0".to_string(), "1".to_string()])
        );

        // Leaf value: empty children
        store
            .write(&path!("leaf"), Record::parsed(Value::from("scalar")))
            .unwrap();
        assert_eq!(store.read_children(&path!("leaf")).unwrap(), Some(vec![]));

        // Missing path: None
        assert_eq!(store.read_children(&path!("missing")).unwrap(), None);
    }

    #[test]
    fn read_children_raw_record_errors() {
        use crate::path;

        let mut store = TestStore::new();
        store
            .write(
                &path!("raw"),
                Record::raw(Bytes::from_static(b"{}"), Format::JSON),
            )
            .unwrap();
        assert!(store.read_children(&path!("raw")).is_err());
    }

    #[test]
    fn read_children_delegates_through_wrappers() {
        use crate::path;

        /// Store that overrides read_children without storing map values.
        struct ListingStore;

        impl Reader for ListingStore {
            fn read(&mut self, _from: &Path) -> Result<Option<Record>, Error> {
                Ok(None)
            }

            fn read_children(&mut self, _from: &Path) -> Result<Option<Vec<String>>, Error> {
                Ok(Some(vec!["custom".to_string()]))
            }
        }

        let mut store = ListingStore;
        let by_ref: &mut dyn Reader = &mut store;
        assert_eq!(
            by_ref.read_children(&path!("x")).unwrap(),
            Some(vec!["custom".to_string()])
        );

        let mut boxed: Box<dyn Reader> = Box::new(ListingStore);
        assert_eq!(
            boxed.read_children(&path!("x")).unwrap(),
            Some(vec!["custom".to_string()])
        );
    }
}