grafos_std/
serde_support.rs

1//! Serde integration for typed read/write of Rust structs to fabric memory.
2//!
3//! Enabled by the `serde` feature flag. Uses [postcard](https://docs.rs/postcard)
4//! for compact, `no_std`-friendly serialization. This module extends
5//! [`FabricMem`] with [`write_struct`](FabricMem::write_struct) and
6//! [`read_struct`](FabricMem::read_struct) methods.
7//!
8//! # Example
9//!
10//! ```rust,ignore
11//! use grafos_std::mem::FabricMem;
12//! use serde::{Serialize, Deserialize};
13//!
14//! #[derive(Serialize, Deserialize, PartialEq, Debug)]
15//! struct Config {
16//!     version: u32,
17//!     name: String,
18//! }
19//!
20//! let mem = FabricMem::hello()?;
21//! let config = Config { version: 1, name: "node-1".into() };
22//!
23//! let written = mem.write_struct(0, &config)?;
24//! let restored: Config = mem.read_struct(0, written as u32)?;
25//! assert_eq!(restored, config);
26//! ```
27
28use serde::{de::DeserializeOwned, Serialize};
29
30use crate::error::{FabricError, Result};
31use crate::mem::FabricMem;
32
33impl FabricMem {
34    /// Serialize `val` and write it to fabric memory at `offset`.
35    ///
36    /// Uses [postcard](https://docs.rs/postcard) encoding, which is compact
37    /// and `no_std`-compatible. Returns the number of bytes written.
38    ///
39    /// # Errors
40    ///
41    /// - [`FabricError::IoError`] if serialization fails (e.g. the type
42    ///   cannot be serialized by postcard).
43    /// - Any [`FabricError`] from the underlying [`FabricMem::write`] call.
44    pub fn write_struct<T: Serialize>(&self, offset: u64, val: &T) -> Result<usize> {
45        let bytes = postcard::to_allocvec(val).map_err(|_| FabricError::IoError(-1))?;
46        self.write(offset, &bytes)?;
47        Ok(bytes.len())
48    }
49
50    /// Read and deserialize a `T` from fabric memory at `offset`.
51    ///
52    /// Reads up to `max_len` bytes from the arena and attempts to
53    /// deserialize them as `T` using postcard.
54    ///
55    /// # Errors
56    ///
57    /// - [`FabricError::IoError`] if deserialization fails (e.g. the data
58    ///   does not match the expected type layout).
59    /// - Any [`FabricError`] from the underlying [`FabricMem::read`] call.
60    pub fn read_struct<T: DeserializeOwned>(&self, offset: u64, max_len: u32) -> Result<T> {
61        let data = self.read(offset, max_len)?;
62        postcard::from_bytes(&data).map_err(|_| FabricError::IoError(-1))
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use crate::host;
70    use serde::{Deserialize, Serialize};
71
72    #[derive(Debug, PartialEq, Serialize, Deserialize)]
73    struct TestPoint {
74        x: u32,
75        y: u32,
76        label: Vec<u8>,
77    }
78
79    #[test]
80    fn serde_roundtrip_struct() {
81        host::reset_mock();
82        host::mock_set_fbmu_arena_size(4096);
83
84        let mem = FabricMem::hello().expect("hello");
85
86        let point = TestPoint {
87            x: 42,
88            y: 99,
89            label: alloc::vec![b'A', b'B', b'C'],
90        };
91
92        let written = mem.write_struct(0, &point).expect("write_struct");
93        assert!(written > 0);
94
95        let restored: TestPoint = mem.read_struct(0, written as u32).expect("read_struct");
96        assert_eq!(restored, point);
97    }
98
99    #[test]
100    fn serde_bad_data_returns_error() {
101        host::reset_mock();
102        host::mock_set_fbmu_arena_size(4096);
103
104        let mem = FabricMem::hello().expect("hello");
105
106        // Write garbage
107        mem.write(0, &[0xFF, 0xFF, 0xFF, 0xFF]).expect("write");
108
109        let result: Result<TestPoint> = mem.read_struct(0, 4);
110        assert!(result.is_err());
111    }
112}