pamoja_codec/lib.rs
1//! Pluggable serialization for pamoja payloads.
2//!
3//! Concrete wire formats - CBOR for constrained devices, Protocol Buffers, JSON,
4//! or raw framing - implement the [`Codec`] trait. This crate defines the trait
5//! and provides serde-based implementations behind feature flags:
6//!
7//! - [`CborCodec`] (feature `cbor`, on by default) - compact binary framing for
8//! constrained devices and metered links.
9//! - [`JsonCodec`] (feature `json`, on by default) - human-readable framing for
10//! interop and debugging.
11//! - [`BytesCodec`] (always available) - a no-op codec that carries raw bytes.
12//!
13//! For metered links it also packs batches of samples into far fewer bytes:
14//! [`encode_deltas`] delta-encodes a series of integers, and [`Quantizer`] rounds
15//! `f32` readings to a fixed precision and delta-encodes them.
16//!
17//! [`json_to_cbor`] and [`cbor_to_json`] convert a whole document between the two
18//! formats without a Rust type for it, which is what a caller holding an untyped
19//! payload needs in order to reach the compact form.
20//!
21//! # Examples
22//!
23//! A little-endian codec for `u32` values:
24//!
25//! ```
26//! use pamoja_codec::Codec;
27//! use pamoja_core::{Error, Result};
28//!
29//! struct LeU32;
30//!
31//! impl Codec<u32> for LeU32 {
32//! fn encode(&self, value: &u32) -> Result<Vec<u8>> {
33//! Ok(value.to_le_bytes().to_vec())
34//! }
35//!
36//! fn decode(&self, bytes: &[u8]) -> Result<u32> {
37//! let array = bytes
38//! .try_into()
39//! .map_err(|_| Error::Codec("expected 4 bytes".into()))?;
40//! Ok(u32::from_le_bytes(array))
41//! }
42//! }
43//!
44//! let codec = LeU32;
45//! let encoded = codec.encode(&42).unwrap();
46//! assert_eq!(codec.decode(&encoded).unwrap(), 42);
47//! ```
48
49use pamoja_core::Result;
50
51mod bytes;
52pub use bytes::BytesCodec;
53
54mod delta;
55pub use delta::{decode_deltas, encode_deltas, Quantizer};
56
57#[cfg(feature = "cbor")]
58mod cbor;
59#[cfg(feature = "cbor")]
60pub use cbor::CborCodec;
61
62#[cfg(feature = "json")]
63mod json;
64#[cfg(feature = "json")]
65pub use json::JsonCodec;
66
67#[cfg(all(feature = "cbor", feature = "json"))]
68mod transcode;
69#[cfg(all(feature = "cbor", feature = "json"))]
70pub use transcode::{cbor_to_json, json_to_cbor};
71
72/// Encodes and decodes values of type `T` to and from byte buffers.
73///
74/// A codec is the bridge between in-memory values and the bytes carried by a
75/// [`Transport`](pamoja_core::Transport) or persisted by a
76/// [`Store`](pamoja_core::Store).
77pub trait Codec<T> {
78 /// Encodes a value into a byte buffer.
79 ///
80 /// # Arguments
81 ///
82 /// * `value` - the value to serialize.
83 ///
84 /// # Returns
85 ///
86 /// A byte buffer containing the encoded representation of `value`.
87 ///
88 /// # Errors
89 ///
90 /// Returns [`Error::Codec`](pamoja_core::Error::Codec) if the value cannot
91 /// be encoded.
92 fn encode(&self, value: &T) -> Result<Vec<u8>>;
93
94 /// Decodes a value from a byte buffer.
95 ///
96 /// # Arguments
97 ///
98 /// * `bytes` - the encoded representation to deserialize.
99 ///
100 /// # Returns
101 ///
102 /// The value decoded from `bytes`.
103 ///
104 /// # Errors
105 ///
106 /// Returns [`Error::Codec`](pamoja_core::Error::Codec) if `bytes` is not a
107 /// valid encoding of `T`.
108 fn decode(&self, bytes: &[u8]) -> Result<T>;
109}