Skip to main content

reliakit_codec/
lib.rs

1//! Deterministic canonical binary encoding and decoding.
2//!
3//! `reliakit-codec` provides small traits and strict primitive implementations
4//! for one canonical binary representation per supported type. It is intended
5//! for simple protocols, fixtures, cache keys, and reliability-oriented library
6//! boundaries where handwritten implementations are preferable to schema or
7//! derive machinery.
8//!
9//! # Example
10//!
11//! ```
12//! # #[cfg(feature = "alloc")]
13//! # {
14//! use reliakit_codec::{decode_from_slice_exact, encode_to_vec, CanonicalDecode, CanonicalEncode};
15//!
16//! #[derive(Debug, PartialEq)]
17//! struct Point {
18//!     x: u16,
19//!     y: u16,
20//! }
21//!
22//! impl CanonicalEncode for Point {
23//!     fn encode<W: reliakit_codec::EncodeSink + ?Sized>(
24//!         &self,
25//!         writer: &mut W,
26//!     ) -> Result<(), reliakit_codec::CodecError> {
27//!         self.x.encode(writer)?;
28//!         self.y.encode(writer)
29//!     }
30//! }
31//!
32//! impl CanonicalDecode for Point {
33//!     fn decode<R: reliakit_codec::DecodeSource + ?Sized>(
34//!         reader: &mut R,
35//!     ) -> Result<Self, reliakit_codec::CodecError> {
36//!         Ok(Self {
37//!             x: u16::decode(reader)?,
38//!             y: u16::decode(reader)?,
39//!         })
40//!     }
41//! }
42//!
43//! let encoded = encode_to_vec(&Point { x: 10, y: 20 })?;
44//! assert_eq!(encoded, [10, 0, 20, 0]);
45//! assert_eq!(decode_from_slice_exact::<Point>(&encoded)?, Point { x: 10, y: 20 });
46//! # Ok::<(), reliakit_codec::CodecError>(())
47//! # }
48//! # #[cfg(not(feature = "alloc"))]
49//! # Ok::<(), reliakit_codec::CodecError>(())
50//! ```
51
52#![cfg_attr(not(feature = "std"), no_std)]
53#![forbid(unsafe_code)]
54#![warn(missing_docs)]
55
56#[cfg(feature = "alloc")]
57extern crate alloc;
58
59/// Decoding traits and byte-slice readers.
60pub mod decode;
61/// Encoding traits and byte sinks.
62pub mod encode;
63/// Error types.
64pub mod error;
65/// Wire format constants and documentation.
66pub mod format;
67/// Convenience helpers.
68pub mod helpers;
69mod impls;
70/// Optional `reliakit-primitives` integrations.
71#[cfg(feature = "primitives")]
72pub mod primitives;
73
74pub use decode::{CanonicalDecode, DecodeSource, SliceReader};
75pub use encode::{CanonicalEncode, EncodeSink};
76pub use error::{CodecError, CodecErrorKind};
77pub use helpers::{decode_from_slice, decode_from_slice_exact};
78
79#[cfg(feature = "alloc")]
80pub use helpers::encode_to_vec;
81
82#[cfg(all(test, feature = "alloc"))]
83mod tests {
84    use super::*;
85    use alloc::string::{String, ToString};
86    use alloc::vec;
87    use alloc::vec::Vec;
88    use core::fmt::Write as _;
89
90    #[derive(Debug, PartialEq, Eq)]
91    struct Message {
92        id: u32,
93        body: String,
94        urgent: bool,
95    }
96
97    impl CanonicalEncode for Message {
98        fn encode<W: EncodeSink + ?Sized>(&self, writer: &mut W) -> Result<(), CodecError> {
99            self.id.encode(writer)?;
100            self.body.encode(writer)?;
101            self.urgent.encode(writer)
102        }
103    }
104
105    impl CanonicalDecode for Message {
106        fn decode<R: DecodeSource + ?Sized>(reader: &mut R) -> Result<Self, CodecError> {
107            Ok(Self {
108                id: u32::decode(reader)?,
109                body: String::decode(reader)?,
110                urgent: bool::decode(reader)?,
111            })
112        }
113    }
114
115    #[test]
116    fn primitive_roundtrips_are_little_endian() {
117        assert_eq!(encode_to_vec(&0x1234u16).unwrap(), vec![0x34, 0x12]);
118        assert_eq!(
119            decode_from_slice_exact::<u16>(&[0x34, 0x12]).unwrap(),
120            0x1234
121        );
122        assert_eq!(encode_to_vec(&-2i16).unwrap(), (-2i16).to_le_bytes());
123    }
124
125    #[test]
126    fn integer_widths_roundtrip() {
127        macro_rules! assert_integer {
128            ($ty:ty, $value:expr) => {
129                let value = $value as $ty;
130                let encoded = encode_to_vec(&value).unwrap();
131                assert_eq!(encoded, value.to_le_bytes());
132                assert_eq!(decode_from_slice_exact::<$ty>(&encoded).unwrap(), value);
133            };
134        }
135
136        assert_integer!(u8, 200);
137        assert_integer!(i8, -12);
138        assert_integer!(u16, 0x1234);
139        assert_integer!(i16, -1234);
140        assert_integer!(u32, 0x1234_5678);
141        assert_integer!(i32, -123_456);
142        assert_integer!(u64, 0x1234_5678_9abc_def0);
143        assert_integer!(i64, -123_456_789);
144        assert_integer!(u128, 0x1234_5678_9abc_def0_1111_2222_3333_4444);
145        assert_integer!(i128, -123_456_789_123_456_789);
146    }
147
148    #[test]
149    fn bool_decode_is_strict() {
150        assert!(!decode_from_slice_exact::<bool>(&[0x00]).unwrap());
151        assert!(decode_from_slice_exact::<bool>(&[0x01]).unwrap());
152        let err = decode_from_slice_exact::<bool>(&[0x02]).unwrap_err();
153        assert_eq!(err.kind(), CodecErrorKind::InvalidValue);
154    }
155
156    #[test]
157    fn string_rejects_invalid_utf8() {
158        let err = decode_from_slice_exact::<String>(&[1, 0, 0, 0, 0xff]).unwrap_err();
159        assert_eq!(err.kind(), CodecErrorKind::InvalidValue);
160    }
161
162    #[test]
163    fn string_rejects_impossible_length_before_allocating() {
164        let err = decode_from_slice_exact::<String>(&u32::MAX.to_le_bytes()).unwrap_err();
165        assert_eq!(err.kind(), CodecErrorKind::UnexpectedEof);
166    }
167
168    #[test]
169    fn length_prefix_controls_string_bytes() {
170        assert_eq!(
171            encode_to_vec("abc").unwrap(),
172            vec![3, 0, 0, 0, b'a', b'b', b'c']
173        );
174        assert_eq!(
175            decode_from_slice_exact::<String>(&[3, 0, 0, 0, b'a', b'b', b'c']).unwrap(),
176            "abc"
177        );
178    }
179
180    #[test]
181    fn exact_decode_rejects_trailing_bytes() {
182        let err = decode_from_slice_exact::<u8>(&[1, 2]).unwrap_err();
183        assert_eq!(err.kind(), CodecErrorKind::TrailingBytes);
184        assert_eq!(decode_from_slice::<u8>(&[1, 2]).unwrap(), (1, 1));
185    }
186
187    #[test]
188    fn slice_reader_reports_remaining_and_eof() {
189        let mut reader = SliceReader::new(&[1, 2]);
190        assert!(!reader.is_empty());
191
192        let mut one = [0u8; 1];
193        reader.read_exact(&mut one).unwrap();
194        assert_eq!(one, [1]);
195        assert_eq!(reader.remaining(), 1);
196
197        let mut two = [0u8; 2];
198        let err = reader.read_exact(&mut two).unwrap_err();
199        assert_eq!(err.kind(), CodecErrorKind::UnexpectedEof);
200    }
201
202    #[test]
203    fn manual_struct_roundtrip() {
204        let message = Message {
205            id: 7,
206            body: String::from("ready"),
207            urgent: true,
208        };
209        let encoded = encode_to_vec(&message).unwrap();
210        assert_eq!(
211            decode_from_slice_exact::<Message>(&encoded).unwrap(),
212            message
213        );
214    }
215
216    #[test]
217    fn invalid_tags_fail() {
218        assert_eq!(
219            decode_from_slice_exact::<Option<u8>>(&[3])
220                .unwrap_err()
221                .kind(),
222            CodecErrorKind::InvalidValue
223        );
224        assert_eq!(
225            decode_from_slice_exact::<Result<u8, u8>>(&[3])
226                .unwrap_err()
227                .kind(),
228            CodecErrorKind::InvalidValue
229        );
230    }
231
232    #[test]
233    fn option_and_result_encode_all_branches() {
234        let none: Option<u16> = None;
235        assert_eq!(encode_to_vec(&none).unwrap(), vec![0]);
236        assert_eq!(decode_from_slice_exact::<Option<u16>>(&[0]).unwrap(), None);
237
238        let some = Some(0x1234u16);
239        assert_eq!(encode_to_vec(&some).unwrap(), vec![1, 0x34, 0x12]);
240        assert_eq!(
241            decode_from_slice_exact::<Option<u16>>(&[1, 0x34, 0x12]).unwrap(),
242            some
243        );
244
245        let ok: Result<u8, u16> = Ok(9);
246        assert_eq!(encode_to_vec(&ok).unwrap(), vec![0, 9]);
247        assert_eq!(
248            decode_from_slice_exact::<Result<u8, u16>>(&[0, 9]).unwrap(),
249            ok
250        );
251
252        let err: Result<u8, u16> = Err(0x1234);
253        assert_eq!(encode_to_vec(&err).unwrap(), vec![1, 0x34, 0x12]);
254        assert_eq!(
255            decode_from_slice_exact::<Result<u8, u16>>(&[1, 0x34, 0x12]).unwrap(),
256            err
257        );
258    }
259
260    #[test]
261    fn vec_and_array_roundtrip() {
262        let values = vec![1u16, 2, 3];
263        let encoded = encode_to_vec(&values).unwrap();
264        assert_eq!(
265            decode_from_slice_exact::<Vec<u16>>(&encoded).unwrap(),
266            values
267        );
268
269        let array = [1u8, 2, 3, 4];
270        let encoded = encode_to_vec(&array).unwrap();
271        assert_eq!(decode_from_slice_exact::<[u8; 4]>(&encoded).unwrap(), array);
272    }
273
274    #[test]
275    fn tuples_roundtrip_by_field_order() {
276        assert_eq!(
277            decode_from_slice_exact::<(u8,)>(&encode_to_vec(&(1u8,)).unwrap()).unwrap(),
278            (1,)
279        );
280        assert_eq!(
281            decode_from_slice_exact::<(u8, u16)>(&encode_to_vec(&(1u8, 0x0203u16)).unwrap())
282                .unwrap(),
283            (1, 0x0203)
284        );
285        assert_eq!(
286            decode_from_slice_exact::<(u8, u16, bool)>(
287                &encode_to_vec(&(1u8, 0x0203u16, true)).unwrap()
288            )
289            .unwrap(),
290            (1, 0x0203, true)
291        );
292        assert_eq!(
293            decode_from_slice_exact::<(u8, u16, bool, i8)>(
294                &encode_to_vec(&(1u8, 0x0203u16, true, -4i8)).unwrap()
295            )
296            .unwrap(),
297            (1, 0x0203, true, -4)
298        );
299    }
300
301    #[test]
302    fn codec_error_accessors_and_display_are_actionable() {
303        let error = CodecError::new(CodecErrorKind::ReadFailed, "reader failed");
304        assert_eq!(error.kind(), CodecErrorKind::ReadFailed);
305        assert_eq!(error.message(), "reader failed");
306        assert_eq!(error.to_string(), "reader failed");
307
308        assert_eq!(CodecError::read_failed().kind(), CodecErrorKind::ReadFailed);
309        assert_eq!(
310            CodecError::write_failed().kind(),
311            CodecErrorKind::WriteFailed
312        );
313        assert_eq!(
314            CodecError::length_overflow("length too large").message(),
315            "length too large"
316        );
317
318        let mut rendered = String::new();
319        write!(&mut rendered, "{}", CodecError::trailing_bytes()).unwrap();
320        assert_eq!(rendered, "decode completed but trailing bytes remain");
321    }
322
323    #[test]
324    #[cfg(feature = "std")]
325    fn std_buf_writer_maps_write_failures() {
326        struct FailingWriter;
327
328        impl std::io::Write for FailingWriter {
329            fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
330                Err(std::io::Error::other("fail"))
331            }
332
333            fn flush(&mut self) -> std::io::Result<()> {
334                Ok(())
335            }
336        }
337
338        let mut writer = std::io::BufWriter::with_capacity(0, FailingWriter);
339        let err = 1u8.encode(&mut writer).unwrap_err();
340        assert_eq!(err.kind(), CodecErrorKind::WriteFailed);
341    }
342}