fuel_data_parser/
lib.rs

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
#![doc = include_str!("../README.md")]

mod compression_strategies;
mod error;

use std::{fmt::Debug, sync::Arc};

pub use compression_strategies::*;

pub use crate::error::{CompressionError, Error, SerdeError};

/// Serialization types supported for data parsing
#[derive(Debug, Clone, strum::EnumIter, strum_macros::Display)]
pub enum SerializationType {
    /// Bincode serialization
    #[strum(serialize = "bincode")]
    Bincode,
    /// Postcard serialization
    #[strum(serialize = "postcard")]
    Postcard,
    /// json serialization
    #[strum(serialize = "json")]
    Json,
}

/// Traits required for a data type to be parseable
pub trait DataParseable:
    serde::Serialize + serde::de::DeserializeOwned + Clone + Send + Sync + Debug
{
}

impl<
        T: serde::Serialize
            + serde::de::DeserializeOwned
            + Clone
            + Send
            + Sync
            + Debug,
    > DataParseable for T
{
}

/// `DataParser` is a utility struct for encoding (serializing and optionally compressing)
/// and decoding (deserializing and optionally decompressing) data. It is useful for
/// optimizing memory usage and I/O bandwidth by applying different
/// serialization formats and optional compression strategies.
///
/// # Fields
///
/// * `compression_strategy` - An `Option<Arc<dyn CompressionStrategy>>` that defines
///   the method of data compression. If `None`, no compression is applied.
/// * `serialization_type` - An enum that specifies the serialization format
///   (e.g., Bincode, Postcard, JSON).
///
/// # Examples
///
/// ```
/// use fuel_data_parser::{DataParser, SerializationType};
/// use std::sync::Arc;
///
/// #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
/// struct TestData {
///     field: String,
/// }
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
///     let parser = DataParser::default();
///
///     let original_data = TestData { field: "test".to_string() };
///     let encoded = parser.encode(&original_data).await?;
///     let decoded: TestData = parser.decode(&encoded).await?;
///
///     assert_eq!(original_data, decoded);
///     Ok(())
/// }
/// ```
#[derive(Clone)]
pub struct DataParser {
    compression_strategy: Option<Arc<dyn CompressionStrategy>>,
    pub serialization_type: SerializationType,
}

impl Default for DataParser {
    /// Provides a default instance of `DataParser` with no compression strategy
    /// and `SerializationType::Json`.
    ///
    /// # Examples
    ///
    /// ```
    /// use fuel_data_parser::{DataParser, SerializationType};
    ///
    /// let parser = DataParser::default();
    /// assert!(matches!(parser.serialization_type, SerializationType::Json));
    /// ```
    fn default() -> Self {
        Self {
            compression_strategy: None,
            serialization_type: SerializationType::Json,
        }
    }
}

impl DataParser {
    /// Sets the compression strategy for the `DataParser`.
    ///
    /// # Arguments
    ///
    /// * `compression_strategy` - A reference to an `Arc` of a `CompressionStrategy` trait object.
    ///
    /// # Returns
    ///
    /// A new instance of `DataParser` with the updated compression strategy.
    ///
    /// # Examples
    ///
    /// ```
    /// use fuel_data_parser::{DataParser, DEFAULT_COMPRESSION_STRATEGY};
    /// use std::sync::Arc;
    ///
    /// let parser = DataParser::default()
    ///     .with_compression_strategy(&DEFAULT_COMPRESSION_STRATEGY);
    /// ```
    pub fn with_compression_strategy(
        mut self,
        compression_strategy: &Arc<dyn CompressionStrategy>,
    ) -> Self {
        self.compression_strategy = Some(compression_strategy.clone());
        self
    }

    /// Sets the serialization type for the `DataParser`.
    ///
    /// # Arguments
    ///
    /// * `serialization_type` - A `SerializationType` enum specifying the desired serialization format.
    ///
    /// # Returns
    ///
    /// A new instance of `DataParser` with the updated serialization type.
    ///
    /// # Examples
    ///
    /// ```
    /// use fuel_data_parser::{DataParser, SerializationType};
    ///
    /// let parser = DataParser::default()
    ///     .with_serialization_type(SerializationType::Json);
    /// ```
    pub fn with_serialization_type(
        mut self,
        serialization_type: SerializationType,
    ) -> Self {
        self.serialization_type = serialization_type;
        self
    }

    /// Encodes the provided data by serializing and optionally compressing it.
    ///
    /// # Arguments
    ///
    /// * `data` - A reference to a data structure implementing the `DataParseable` trait.
    ///
    /// # Returns
    ///
    /// A `Result` containing either a `Vec<u8>` of the serialized (and optionally compressed) data,
    /// or an `Error` if encoding fails.
    ///
    /// # Examples
    ///
    /// ```
    /// use fuel_data_parser::DataParser;
    ///
    /// #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
    /// struct TestData {
    ///     field: String,
    /// }
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let parser = DataParser::default();
    ///     let data = TestData { field: "test".to_string() };
    ///     let encoded = parser.encode(&data).await?;
    ///     assert!(!encoded.is_empty());
    ///     Ok(())
    /// }
    /// ```
    pub async fn encode<T: DataParseable>(
        &self,
        data: &T,
    ) -> Result<Vec<u8>, Error> {
        let serialized_data = self.serialize(data).await?;
        Ok(match &self.compression_strategy {
            Some(strategy) => strategy.compress(&serialized_data[..]).await?,
            None => serialized_data,
        })
    }

    /// Serializes the provided data according to the selected `SerializationType`.
    ///
    /// # Arguments
    ///
    /// * `raw_data` - A reference to a data structure implementing the `DataParseable` trait.
    ///
    /// # Returns
    ///
    /// A `Result` containing either a `Vec<u8>` of the serialized data,
    /// or an `Error` if serialization fails.
    pub async fn serialize<T: DataParseable>(
        &self,
        raw_data: &T,
    ) -> Result<Vec<u8>, Error> {
        match self.serialization_type {
            SerializationType::Bincode => bincode::serialize(&raw_data)
                .map_err(|e| Error::Serde(SerdeError::Bincode(*e))),
            SerializationType::Postcard => postcard::to_allocvec(&raw_data)
                .map_err(|e| Error::Serde(SerdeError::Postcard(e))),
            SerializationType::Json => serde_json::to_vec(&raw_data)
                .map_err(|e| Error::Serde(SerdeError::Json(e))),
        }
    }

    /// Decodes the provided data by deserializing and optionally decompressing it.
    ///
    /// # Arguments
    ///
    /// * `data` - A byte slice (`&[u8]`) representing the serialized (and optionally compressed) data.
    ///
    /// # Returns
    ///
    /// A `Result` containing either the deserialized data structure,
    /// or an `Error` if decoding fails.
    ///
    /// # Examples
    ///
    /// ```
    /// use fuel_data_parser::DataParser;
    ///
    /// #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
    /// struct TestData {
    ///     field: String,
    /// }
    ///
    /// #[tokio::main]
    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
    ///     let parser = DataParser::default();
    ///     let original_data = TestData { field: "test".to_string() };
    ///     let encoded = parser.encode(&original_data).await?;
    ///     let decoded: TestData = parser.decode(&encoded).await?;
    ///     assert_eq!(original_data, decoded);
    ///     Ok(())
    /// }
    /// ```
    pub async fn decode<T: DataParseable>(
        &self,
        data: &[u8],
    ) -> Result<T, Error> {
        let data = match &self.compression_strategy {
            Some(strategy) => strategy.decompress(data).await?,
            None => data.to_vec(),
        };
        let decoded_data = self.deserialize(&data[..]).await?;
        Ok(decoded_data)
    }

    /// Deserializes the provided data according to the selected `SerializationType`.
    ///
    /// # Arguments
    ///
    /// * `raw_data` - A byte slice (`&[u8]`) representing the serialized data.
    ///
    /// # Returns
    ///
    /// A `Result` containing either the deserialized data structure,
    /// or an `Error` if deserialization fails.
    pub async fn deserialize<'a, T: serde::Deserialize<'a>>(
        &self,
        raw_data: &'a [u8],
    ) -> Result<T, Error> {
        match self.serialization_type {
            SerializationType::Bincode => bincode::deserialize(raw_data)
                .map_err(|e| Error::Serde(SerdeError::Bincode(*e))),
            SerializationType::Postcard => postcard::from_bytes(raw_data)
                .map_err(|e| Error::Serde(SerdeError::Postcard(e))),
            SerializationType::Json => serde_json::from_slice(raw_data)
                .map_err(|e| Error::Serde(SerdeError::Json(e))),
        }
    }
}

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

    #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
    struct TestData {
        field: String,
    }

    #[tokio::test]
    async fn test_encode_decode() {
        let parser = DataParser::default();
        let original_data = TestData {
            field: "test".to_string(),
        };
        let encoded = parser.encode(&original_data).await.unwrap();
        let decoded: TestData = parser.decode(&encoded).await.unwrap();
        assert_eq!(original_data, decoded);
    }

    #[tokio::test]
    async fn test_serialization_types() {
        let data = TestData {
            field: "test".to_string(),
        };

        for serialization_type in [
            SerializationType::Bincode,
            SerializationType::Postcard,
            SerializationType::Json,
        ] {
            let parser = DataParser::default()
                .with_serialization_type(serialization_type);
            let encoded = parser.encode(&data).await.unwrap();
            let decoded: TestData = parser.decode(&encoded).await.unwrap();
            assert_eq!(data, decoded);
        }
    }

    #[tokio::test]
    async fn test_compression_strategies() {
        let data = TestData {
            field: "test".to_string(),
        };
        let compression_strategies: Vec<Arc<dyn CompressionStrategy>> = vec![
            Arc::new(ZLibCompressionStrategy),
            #[cfg(feature = "bench-helpers")]
            Arc::new(GzipCompressionStrategy),
            #[cfg(feature = "bench-helpers")]
            Arc::new(BrotliCompressionStrategy),
        ];

        for strategy in compression_strategies {
            let parser =
                DataParser::default().with_compression_strategy(&strategy);
            let encoded = parser.encode(&data).await.unwrap();
            let decoded: TestData = parser.decode(&encoded).await.unwrap();
            assert_eq!(data, decoded);
        }
    }
}