epserde/ser/write_with_names.rs
1/*
2 * SPDX-FileCopyrightText: 2023 Inria
3 * SPDX-FileCopyrightText: 2023 Sebastiano Vigna
4 *
5 * SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
6 */
7
8//! Traits and implementations to write named field during serialization.
9//!
10//! [`SerInner::_ser_inner`] writes on a [`WriteWithNames`], rather
11//! than on a [`WriteWithPos`], with the purpose of easily recording write
12//! events happening during a serialization.
13
14use super::*;
15
16#[cfg(not(feature = "std"))]
17use alloc::{
18 format,
19 string::{String, ToString},
20 vec::Vec,
21};
22
23/// Trait extending [`WriteWithPos`] with methods providing alignment,
24/// serialization of named data, and writing of byte slices of zero-copy types.
25///
26/// The purpose of this trait is that of interposing between [`SerInner`] and
27/// the underlying [`WriteWithPos`] a layer in which serialization operations
28/// can be easily intercepted and recorded. In particular, serialization methods
29/// must use the methods of this trait if they want to record the schema of the
30/// serialized data; this is true (maybe counterintuitively) even of ancillary
31/// data such as tags and slice lengths: see [`helpers`] or the [implementation
32/// of `Option`](impls::prim) for examples. All methods have a default
33/// implementation that must be replicated in other implementations.
34///
35/// There are two implementations of [`WriteWithNames`]: [`WriterWithPos`],
36/// which uses the default implementation, and [`SchemaWriter`], which
37/// additionally records a [`Schema`] of the serialized data.
38pub trait WriteWithNames: WriteWithPos + Sized {
39 /// Add some zero padding so that `self.pos() % V:align_to() == 0.`
40 ///
41 /// Other implementations must write the same number of zeros.
42 fn align<V: AlignTo>(&mut self) -> Result<()> {
43 let padding = pad_align_to(self.pos(), V::align_to());
44 for _ in 0..padding {
45 self.write_all(&[0])?;
46 }
47 Ok(())
48 }
49
50 /// Writes a value with an associated name.
51 ///
52 /// The default implementation simply delegates to [`SerInner::_ser_inner`].
53 /// Other implementations might use the name information (e.g., [`SchemaWriter`]),
54 /// but they must in the end delegate to [`SerInner::_ser_inner`].
55 fn write<V: SerInner>(&mut self, _field_name: &str, value: &V) -> Result<()> {
56 unsafe { value._ser_inner(self) }
57 }
58
59 /// Write the memory representation of a (slice of a) zero-copy type.
60 ///
61 /// The default implementation simply delegates to [`WriteNoStd::write_all`].
62 /// Other implementations might use the type information in `V` (e.g., [`SchemaWriter`]),
63 /// but they must in the end delegate to [`WriteNoStd::write_all`].
64 fn write_bytes<V: SerInner + ZeroCopy>(&mut self, value: &[u8]) -> Result<()> {
65 self.write_all(value)
66 }
67}
68
69impl<F: WriteNoStd> WriteWithNames for WriterWithPos<'_, F> {}
70
71/// Information about data written during serialization, either fields or
72/// ancillary data such as option tags and slice lengths.
73#[derive(Debug, Clone)]
74#[cfg_attr(feature = "mem_dbg", derive(mem_dbg::MemDbg, mem_dbg::MemSize))]
75pub struct SchemaRow {
76 /// Name of the piece of data.
77 pub field: String,
78 /// Type of the piece of data.
79 pub ty: String,
80 /// Offset from the start of the file.
81 pub offset: usize,
82 /// Length in bytes of the piece of data.
83 pub size: usize,
84 /// The alignment needed by the piece of data, zero if not applicable
85 /// (e.g., primitive fields, ancillary data, or structures).
86 pub align: usize,
87}
88
89#[derive(Default, Debug, Clone)]
90#[cfg_attr(feature = "mem_dbg", derive(mem_dbg::MemDbg, mem_dbg::MemSize))]
91/// A vector containing all the fields written during serialization, including
92/// ancillary data such as slice lengths and [`Option`] tags.
93pub struct Schema(pub Vec<SchemaRow>);
94
95impl Schema {
96 /// Returns a CSV representation of the schema, including data.
97 ///
98 /// WARNING: the size of the CSV will be larger than the size of the
99 /// serialized file, so it is not a good idea to call this method
100 /// on big structures.
101 pub fn debug(&self, data: &[u8]) -> String {
102 let mut result = "field,offset,align,size,ty,bytes\n".to_string();
103 for i in 0..self.0.len().saturating_sub(1) {
104 let row = &self.0[i];
105 // if it's a composed type, don't print the bytes
106 if row.offset == self.0[i + 1].offset {
107 result.push_str(&format!(
108 "{},{},{},{},{},\n",
109 row.field, row.offset, row.align, row.size, row.ty,
110 ));
111 } else {
112 result.push_str(&format!(
113 "{},{},{},{},{},{:02x?}\n",
114 row.field,
115 row.offset,
116 row.align,
117 row.size,
118 row.ty,
119 &data[row.offset..row.offset + row.size],
120 ));
121 }
122 }
123
124 // the last field can't be a composed type by definition
125 if let Some(row) = self.0.last() {
126 result.push_str(&format!(
127 "{},{},{},{},{},{:02x?}\n",
128 row.field,
129 row.offset,
130 row.align,
131 row.size,
132 row.ty,
133 &data[row.offset..row.offset + row.size],
134 ));
135 }
136
137 result
138 }
139
140 /// Return a CSV representation of the schema, excluding data.
141 pub fn to_csv(&self) -> String {
142 let mut result = "field,offset,align,size,ty\n".to_string();
143 for row in &self.0 {
144 result.push_str(&format!(
145 "{},{},{},{},{}\n",
146 row.field, row.offset, row.align, row.size, row.ty
147 ));
148 }
149 result
150 }
151}
152
153/// A [`WriteWithNames`] that keeps track of the data written on an underlying
154/// [`WriteWithPos`] in a [`Schema`].
155#[derive(Debug)]
156#[cfg_attr(feature = "mem_dbg", derive(mem_dbg::MemDbg, mem_dbg::MemSize))]
157pub struct SchemaWriter<'a, W> {
158 /// The schema so far.
159 pub schema: Schema,
160 /// A recursively-built sequence of previous names.
161 path: Vec<String>,
162 /// What we actually write on.
163 writer: &'a mut W,
164}
165
166impl<'a, W: WriteWithPos> SchemaWriter<'a, W> {
167 /// Create a new empty [`SchemaWriter`] on top of a generic writer `W`.
168 pub fn new(backend: &'a mut W) -> Self {
169 Self {
170 schema: Default::default(),
171 path: Vec::new(),
172 writer: backend,
173 }
174 }
175}
176impl<W: WriteNoStd> WriteNoStd for SchemaWriter<'_, W> {
177 fn write_all(&mut self, buf: &[u8]) -> ser::Result<()> {
178 self.writer.write_all(buf)
179 }
180
181 fn flush(&mut self) -> ser::Result<()> {
182 self.writer.flush()
183 }
184}
185
186impl<W: WriteWithPos> WriteWithPos for SchemaWriter<'_, W> {
187 fn pos(&self) -> usize {
188 self.writer.pos()
189 }
190}
191
192/// WARNING: these implementations must be kept in sync with the ones
193/// in the default implementation of [`WriteWithNames`].
194impl<W: WriteWithPos> WriteWithNames for SchemaWriter<'_, W> {
195 fn align<T: AlignTo>(&mut self) -> Result<()> {
196 let padding = pad_align_to(self.pos(), T::align_to());
197 if padding != 0 {
198 self.schema.0.push(SchemaRow {
199 field: "PADDING".into(),
200 ty: format!("[u8; {}]", padding),
201 offset: self.pos(),
202 size: padding,
203 align: 1,
204 });
205 for _ in 0..padding {
206 self.write_all(&[0])?;
207 }
208 }
209
210 Ok(())
211 }
212
213 fn write<V: SerInner>(&mut self, field_name: &str, value: &V) -> Result<()> {
214 // prepare a row with the field name and the type
215 self.path.push(field_name.into());
216 let pos = self.pos();
217
218 let len = self.schema.0.len();
219 unsafe { value._ser_inner(self)? };
220
221 // This is slightly inefficient because we have to shift
222 // the whole vector, but it's not a big deal and it keeps
223 // the schema in the correct order.
224 self.schema.0.insert(
225 len,
226 SchemaRow {
227 field: self.path.join("."),
228 ty: core::any::type_name::<V>().to_string(),
229 offset: pos,
230 align: 0,
231 size: self.pos() - pos,
232 },
233 );
234 self.path.pop();
235 Ok(())
236 }
237
238 fn write_bytes<V: SerInner + ZeroCopy>(&mut self, value: &[u8]) -> Result<()> {
239 self.path.push("zero".to_string());
240 // Note that we are writing the schema row of the field before
241 // having written its content.
242 self.schema.0.push(SchemaRow {
243 field: self.path.join("."),
244 ty: core::any::type_name::<V>().to_string(),
245 offset: self.pos(),
246 size: value.len(),
247 align: V::align_to(),
248 });
249 self.path.pop();
250
251 self.write_all(value)
252 }
253}