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}