Skip to main content

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//! The crate is `no_std` and needs only `alloc`. The [`Codec`] trait, [`BytesCodec`],
22//! and the packers build for a microcontroller with default features off; the CBOR and
23//! JSON codecs rely on libraries that use the standard library.
24//!
25//! # Examples
26//!
27//! A little-endian codec for `u32` values:
28//!
29//! ```
30//! use pamoja_codec::Codec;
31//! use pamoja_core::{Error, Result};
32//!
33//! struct LeU32;
34//!
35//! impl Codec<u32> for LeU32 {
36//!     fn encode(&self, value: &u32) -> Result<Vec<u8>> {
37//!         Ok(value.to_le_bytes().to_vec())
38//!     }
39//!
40//!     fn decode(&self, bytes: &[u8]) -> Result<u32> {
41//!         let array = bytes
42//!             .try_into()
43//!             .map_err(|_| Error::Codec("expected 4 bytes".into()))?;
44//!         Ok(u32::from_le_bytes(array))
45//!     }
46//! }
47//!
48//! let codec = LeU32;
49//! let encoded = codec.encode(&42).unwrap();
50//! assert_eq!(codec.decode(&encoded).unwrap(), 42);
51//! ```
52
53#![cfg_attr(not(test), no_std)]
54
55extern crate alloc;
56
57use alloc::vec::Vec;
58
59use pamoja_core::Result;
60
61mod bytes;
62pub use bytes::BytesCodec;
63
64mod delta;
65pub use delta::{decode_deltas, encode_deltas, Quantizer};
66
67#[cfg(feature = "cbor")]
68mod cbor;
69#[cfg(feature = "cbor")]
70pub use cbor::CborCodec;
71
72#[cfg(feature = "json")]
73mod json;
74#[cfg(feature = "json")]
75pub use json::JsonCodec;
76
77#[cfg(all(feature = "cbor", feature = "json"))]
78mod transcode;
79#[cfg(all(feature = "cbor", feature = "json"))]
80pub use transcode::{cbor_to_json, json_to_cbor};
81
82/// Encodes and decodes values of type `T` to and from byte buffers.
83///
84/// A codec is the bridge between in-memory values and the bytes carried by a
85/// [`Transport`](pamoja_core::Transport) or persisted by a
86/// [`Store`](pamoja_core::Store).
87pub trait Codec<T> {
88    /// Encodes a value into a byte buffer.
89    ///
90    /// # Arguments
91    ///
92    /// * `value` - the value to serialize.
93    ///
94    /// # Returns
95    ///
96    /// A byte buffer containing the encoded representation of `value`.
97    ///
98    /// # Errors
99    ///
100    /// Returns [`Error::Codec`](pamoja_core::Error::Codec) if the value cannot
101    /// be encoded.
102    fn encode(&self, value: &T) -> Result<Vec<u8>>;
103
104    /// Decodes a value from a byte buffer.
105    ///
106    /// # Arguments
107    ///
108    /// * `bytes` - the encoded representation to deserialize.
109    ///
110    /// # Returns
111    ///
112    /// The value decoded from `bytes`.
113    ///
114    /// # Errors
115    ///
116    /// Returns [`Error::Codec`](pamoja_core::Error::Codec) if `bytes` is not a
117    /// valid encoding of `T`.
118    fn decode(&self, bytes: &[u8]) -> Result<T>;
119}