1use serde::{Serialize, de::DeserializeOwned};
7use snafu::{ResultExt, Snafu};
8
9#[derive(Debug, Snafu)]
11pub enum CodecError {
12 #[snafu(display("Encoding failed: {source}"))]
14 Encode {
15 source: postcard::Error,
17 #[snafu(implicit)]
19 location: snafu::Location,
20 },
21
22 #[snafu(display("Decoding failed: {source}"))]
24 Decode {
25 source: postcard::Error,
27 #[snafu(implicit)]
29 location: snafu::Location,
30 },
31}
32
33pub fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>, CodecError> {
39 postcard::to_allocvec(value).context(EncodeSnafu)
40}
41
42pub fn decode<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, CodecError> {
48 postcard::from_bytes(bytes).context(DecodeSnafu)
49}
50
51#[cfg(test)]
52#[allow(clippy::unwrap_used, clippy::expect_used, clippy::disallowed_methods)]
53mod tests {
54 use serde::Deserialize;
55 use snafu::ResultExt;
56
57 use super::*;
58
59 #[test]
61 fn test_roundtrip_primitive_u64() {
62 let original: u64 = 42;
63 let bytes = encode(&original).expect("encode u64");
64 let decoded: u64 = decode(&bytes).expect("decode u64");
65 assert_eq!(original, decoded);
66 }
67
68 #[test]
69 fn test_roundtrip_primitive_string() {
70 let original = "hello world".to_string();
71 let bytes = encode(&original).expect("encode string");
72 let decoded: String = decode(&bytes).expect("decode string");
73 assert_eq!(original, decoded);
74 }
75
76 #[test]
77 fn test_roundtrip_primitive_bool() {
78 for original in [true, false] {
79 let bytes = encode(&original).expect("encode bool");
80 let decoded: bool = decode(&bytes).expect("decode bool");
81 assert_eq!(original, decoded);
82 }
83 }
84
85 #[test]
86 fn test_roundtrip_primitive_vec() {
87 let original: Vec<u32> = vec![1, 2, 3, 4, 5];
88 let bytes = encode(&original).expect("encode vec");
89 let decoded: Vec<u32> = decode(&bytes).expect("decode vec");
90 assert_eq!(original, decoded);
91 }
92
93 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95 struct ComplexStruct {
96 id: u64,
97 name: String,
98 data: Vec<u8>,
99 nested: Option<NestedStruct>,
100 }
101
102 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
103 struct NestedStruct {
104 value: i32,
105 flag: bool,
106 }
107
108 #[test]
109 fn test_roundtrip_complex_struct() {
110 let original = ComplexStruct {
111 id: 12345,
112 name: "test entity".to_string(),
113 data: vec![0xDE, 0xAD, 0xBE, 0xEF],
114 nested: Some(NestedStruct { value: -42, flag: true }),
115 };
116 let bytes = encode(&original).expect("encode complex struct");
117 let decoded: ComplexStruct = decode(&bytes).expect("decode complex struct");
118 assert_eq!(original, decoded);
119 }
120
121 #[test]
122 fn test_roundtrip_complex_struct_with_none() {
123 let original = ComplexStruct { id: 0, name: String::new(), data: vec![], nested: None };
124 let bytes = encode(&original).expect("encode complex struct with None");
125 let decoded: ComplexStruct = decode(&bytes).expect("decode complex struct with None");
126 assert_eq!(original, decoded);
127 }
128
129 #[test]
131 fn test_decode_malformed_input() {
132 let malformed_bytes = [0xFF, 0xFF, 0xFF, 0xFF];
133 let result: Result<ComplexStruct, _> = decode(&malformed_bytes);
134 assert!(result.is_err());
135 let err = result.unwrap_err();
136 assert!(matches!(err, CodecError::Decode { .. }));
137 let display = err.to_string();
139 assert!(display.contains("Decoding failed"));
140 }
141
142 #[test]
143 fn test_decode_truncated_data() {
144 let original = ComplexStruct {
146 id: 12345,
147 name: "test".to_string(),
148 data: vec![1, 2, 3],
149 nested: None,
150 };
151 let bytes = encode(&original).expect("encode");
152 let truncated = &bytes[..2.min(bytes.len())];
154 let result: Result<ComplexStruct, _> = decode(truncated);
155 assert!(result.is_err());
156 }
157
158 #[test]
160 fn test_decode_empty_input() {
161 let empty: &[u8] = &[];
162 let result: Result<u64, _> = decode(empty);
163 assert!(result.is_err());
164 let err = result.unwrap_err();
165 assert!(matches!(err, CodecError::Decode { .. }));
166 }
167
168 #[test]
169 fn test_encode_empty_vec() {
170 let empty_vec: Vec<u8> = vec![];
171 let bytes = encode(&empty_vec).expect("encode empty vec");
172 let decoded: Vec<u8> = decode(&bytes).expect("decode empty vec");
173 assert_eq!(empty_vec, decoded);
174 }
175
176 #[test]
177 fn test_encode_empty_string() {
178 let empty_string = String::new();
179 let bytes = encode(&empty_string).expect("encode empty string");
180 let decoded: String = decode(&bytes).expect("decode empty string");
181 assert_eq!(empty_string, decoded);
182 }
183
184 #[test]
186 fn test_encode_error_display() {
187 let malformed: &[u8] = &[0xFF];
190 let result: Result<String, _> = decode(malformed);
191 let err = result.unwrap_err();
192 let display = err.to_string();
193 assert!(display.starts_with("Decoding failed:"));
194 }
195
196 #[test]
198 fn test_roundtrip_max_u64() {
199 let original: u64 = u64::MAX;
200 let bytes = encode(&original).expect("encode max u64");
201 let decoded: u64 = decode(&bytes).expect("decode max u64");
202 assert_eq!(original, decoded);
203 }
204
205 #[test]
206 fn test_roundtrip_unicode_string() {
207 let original = "Hello 世界 🦀 émoji".to_string();
208 let bytes = encode(&original).expect("encode unicode");
209 let decoded: String = decode(&bytes).expect("decode unicode");
210 assert_eq!(original, decoded);
211 }
212
213 #[test]
219 fn test_codec_error_encode_display() {
220 let malformed: &[u8] = &[0xFF, 0xFF, 0xFF, 0xFF];
222 let result: Result<u64, _> = decode(malformed);
223 let err = result.unwrap_err();
224
225 let display = format!("{err}");
227 assert!(
228 display.starts_with("Decoding failed:"),
229 "Expected 'Decoding failed:', got: {display}"
230 );
231 }
232
233 #[test]
235 fn test_codec_error_decode_display() {
236 let empty: &[u8] = &[];
237 let result: Result<String, _> = decode(empty);
238 let err = result.unwrap_err();
239
240 let display = format!("{err}");
241 assert!(
242 display.starts_with("Decoding failed:"),
243 "Expected 'Decoding failed:', got: {display}"
244 );
245 }
246
247 #[test]
249 fn test_codec_error_source_chain() {
250 use std::error::Error;
251
252 let malformed: &[u8] = &[0xFF];
253 let result: Result<String, _> = decode(malformed);
254 let err = result.unwrap_err();
255
256 assert!(err.source().is_some(), "CodecError should have a source");
258
259 let source = err.source().unwrap();
261 let source_display = format!("{source}");
263 assert!(!source_display.is_empty(), "Source should have non-empty display");
264 }
265
266 #[test]
268 fn test_codec_error_debug() {
269 let malformed: &[u8] = &[0xFF, 0xFF];
270 let result: Result<u64, _> = decode(malformed);
271 let err = result.unwrap_err();
272
273 let debug = format!("{err:?}");
274 assert!(debug.contains("Decode"), "Debug should contain 'Decode' variant name");
276 assert!(debug.contains("source"), "Debug should contain 'source' field: {debug}");
278 }
279
280 #[test]
282 fn test_codec_error_variants() {
283 let decode_result: Result<u64, CodecError> =
285 postcard::from_bytes::<u64>(&[0xFF, 0xFF, 0xFF]).context(super::DecodeSnafu);
286 let decode_err = decode_result.expect_err("should fail");
287
288 assert!(matches!(decode_err, CodecError::Decode { .. }));
290
291 assert!(!matches!(decode_err, CodecError::Encode { .. }));
295 }
296}