diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..5122b87a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,5 @@ +# see https://editorconfig.org for more options, and setup instructions for yours editor + +[*.rs] +indent_style = space +indent_size = 4 diff --git a/Cargo.toml b/Cargo.toml index 8b5c14c4..3fe3023b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ maintenance = { status = "passively-maintained" } [dependencies] half = "1.2.0" serde = { version = "1.0.14", default-features = false } +tokio-util = { version = "0.3", optional = true } +bytes = { version = "0.5", optional = true } [dev-dependencies] serde_derive = { version = "1.0.14", default-features = false } @@ -31,3 +33,4 @@ alloc = ["serde/alloc"] std = ["serde/std" ] unsealed_read_write = [] tags = [] +codec = ["tokio-util/codec", "bytes"] diff --git a/src/codec.rs b/src/codec.rs new file mode 100644 index 00000000..3efc071a --- /dev/null +++ b/src/codec.rs @@ -0,0 +1,85 @@ +//! Encoding and decoding for tokio + +use crate::error::Category; +use crate::Error; + +use std::marker::PhantomData; + +use bytes::{buf::ext::BufMutExt, buf::Buf, BytesMut}; +use serde::de::DeserializeOwned; +use serde::Serialize; +use tokio_util::codec; + +/// A `tokio_util::codec::Encoder` for CBOR frames +pub struct Encoder(PhantomData); + +impl Default for Encoder { + fn default() -> Self { + Self(PhantomData) + } +} + +impl codec::Encoder<&T> for Encoder { + type Error = Error; + + fn encode(&mut self, item: &T, dst: &mut BytesMut) -> Result<(), Error> { + crate::to_writer(dst.writer(), item) + } +} + +/// A `tokio_util::codec::Decoder` for CBOR frames +pub struct Decoder(PhantomData); + +impl Default for Decoder { + fn default() -> Self { + Self(PhantomData) + } +} + +impl codec::Decoder for Decoder { + type Item = T; + type Error = Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Error> { + let mut bytes: &[u8] = src.as_ref(); + let starting = bytes.len(); + + let item: T = match crate::from_reader(&mut bytes) { + Err(e) if e.classify() == Category::Eof => return Ok(None), + Ok(v) => v, + e => e?, + }; + + let ending = bytes.len(); + src.advance(starting - ending); + Ok(Some(item)) + } +} + +/// A Codec for CBOR frames +pub struct Codec(Encoder, Decoder); + +impl Default for Codec { + fn default() -> Self { + Codec(Encoder::default(), Decoder::default()) + } +} + +impl codec::Encoder<&T> for Codec { + type Error = Error; + + #[inline] + fn encode(&mut self, item: &T, dst: &mut BytesMut) -> Result<(), Error> { + self.0.encode(item, dst) + } +} + +impl codec::Decoder for Codec { + type Item = U; + type Error = Error; + + #[inline] + fn decode(&mut self, src: &mut BytesMut) -> Result, Error> { + self.1.decode(src) + } +} diff --git a/src/lib.rs b/src/lib.rs index 1736ce8d..4ce31d65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,11 +66,20 @@ //! # Packed Encoding //! When serializing structs or enums in CBOR the keys or enum variant names will be serialized //! as string keys to a map. Especially in embedded environments this can increase the file -//! size too much. In packed encoding the keys and variants will be serialized as variable sized -//! integers. The first 24 entries in any struct consume only a single byte! +//! size too much. In packed encoding all struct keys, as well as any enum variant that has no data, +//! will be serialized as variable sized integers. The first 24 entries in any struct consume only a +//! single byte! Packed encoding uses serde's preferred [externally tagged enum +//! format](https://serde.rs/enum-representations.html) and therefore serializes enum variant names +//! as string keys when that variant contains data. So, in the packed encoding example, `FirstVariant` +//! encodes to a single byte, but encoding `SecondVariant` requires 16 bytes. +//! //! To serialize a document in this format use `Serializer::new(writer).packed_format()` or //! the shorthand `ser::to_vec_packed`. The deserialization works without any changes. //! +//! If you would like to omit the enum variant encoding for all variants, including ones that +//! contain data, you can add `legacy_enums()` in addition to `packed_format()`, as can seen +//! in the Serialize using minimal encoding example. +//! //! # Self describing documents //! In some contexts different formats are used but there is no way to declare the format used //! out of band. For this reason CBOR has a magic number that may be added before any document. @@ -132,6 +141,55 @@ //! # } //! ``` //! +//! Serialize using packed encoding +//! +//! ```rust +//! use serde_derive::{Deserialize, Serialize}; +//! use serde_cbor::ser::to_vec_packed; +//! use WithTwoVariants::*; +//! +//! #[derive(Debug, Serialize, Deserialize)] +//! enum WithTwoVariants { +//! FirstVariant, +//! SecondVariant(u8), +//! } +//! +//! let cbor = to_vec_packed(&FirstVariant).unwrap(); +//! assert_eq!(cbor.len(), 1); +//! +//! let cbor = to_vec_packed(&SecondVariant(0)).unwrap(); +//! assert_eq!(cbor.len(), 16); // Includes 13 bytes of "SecondVariant" +//! ``` +//! +//! Serialize using minimal encoding +//! +//! ```rust +//! use serde_derive::{Deserialize, Serialize}; +//! use serde_cbor::{Result, Serializer, ser::{self, IoWrite}}; +//! use WithTwoVariants::*; +//! +//! fn to_vec_minimal(value: &T) -> Result> +//! where +//! T: serde::Serialize, +//! { +//! let mut vec = Vec::new(); +//! value.serialize(&mut Serializer::new(&mut IoWrite::new(&mut vec)).packed_format().legacy_enums())?; +//! Ok(vec) +//! } +//! +//! #[derive(Debug, Serialize, Deserialize)] +//! enum WithTwoVariants { +//! FirstVariant, +//! SecondVariant(u8), +//! } +//! +//! let cbor = to_vec_minimal(&FirstVariant).unwrap(); +//! assert_eq!(cbor.len(), 1); +//! +//! let cbor = to_vec_minimal(&SecondVariant(0)).unwrap(); +//! assert_eq!(cbor.len(), 3); +//! ``` +//! //! # `no-std` support //! //! Serde CBOR supports building in a `no_std` context, use the following lines @@ -277,6 +335,9 @@ mod write; #[cfg(feature = "std")] pub mod value; +#[cfg(feature = "codec")] +pub mod codec; + // Re-export the [items recommended by serde](https://serde.rs/conventions.html). #[doc(inline)] pub use crate::de::{Deserializer, StreamDeserializer}; diff --git a/src/tags.rs b/src/tags.rs index 50e039e7..8adccb8e 100644 --- a/src/tags.rs +++ b/src/tags.rs @@ -42,8 +42,7 @@ fn untagged(value: T) -> Tagged { macro_rules! delegate { ($name: ident, $type: ty) => { - fn $name(self, v: $type) -> Result - { + fn $name(self, v: $type) -> Result { T::deserialize(v.into_deserializer()).map(untagged) } }; diff --git a/tests/codec.rs b/tests/codec.rs new file mode 100644 index 00000000..fa813a87 --- /dev/null +++ b/tests/codec.rs @@ -0,0 +1,47 @@ +#![cfg(feature = "codec")] + +use bytes::{BufMut, BytesMut}; +use serde_cbor::{codec::Codec, error::Category}; +use tokio_util::codec::{Decoder, Encoder}; + +#[test] +fn decode() { + let mut codec = Codec::::default(); + + assert_eq!( + codec + .decode(&mut b"\xFF"[..].into()) + .err() + .unwrap() + .classify(), + Category::Syntax + ); + + assert_eq!( + codec + .decode(&mut b"\x24"[..].into()) + .err() + .unwrap() + .classify(), + Category::Data + ); + + assert_eq!(codec.decode(&mut b"\x07"[..].into()).unwrap().unwrap(), 7); + + let mut buf = BytesMut::with_capacity(2); + assert_eq!(codec.decode(&mut buf).unwrap(), None); + buf.put_u8(0x18); + assert_eq!(codec.decode(&mut buf).unwrap(), None); + buf.put_u8(0x18); + assert_eq!(codec.decode(&mut buf).unwrap(), Some(24)); +} + +#[test] +fn encode() { + let mut codec = Codec::::default(); + let mut buf = BytesMut::new(); + + codec.encode(&7, &mut buf).unwrap(); + assert_eq!(buf.len(), 1); + assert_eq!(buf[0], 7); +}