Skip to main content

multi_cbor/
lib.rs

1//! CBOR and serialization.
2//!
3//! # Usage
4//!
5//! `multi-cbor` is a fork of the archived upstream `serde_cbor` crate, renamed
6//! and extended with a `tags` feature for [DAG-CBOR] tag round-tripping. Add
7//! this to your `Cargo.toml`:
8//! ```toml
9//! [dependencies]
10//! multi_cbor = "0.1"
11//! ```
12//!
13//! [DAG-CBOR]: https://github.com/ipld/carbites/blob/main/dag-cbor.md
14//!
15//! Storing and loading Rust types is easy and requires only
16//! minimal modifications to the program code.
17//!
18//! ```rust
19//! use serde_derive::{Deserialize, Serialize};
20//! use std::error::Error;
21//! use std::fs::File;
22//!
23//! // Types annotated with `Serialize` can be stored as CBOR.
24//! // To be able to load them again add `Deserialize`.
25//! #[derive(Debug, Serialize, Deserialize)]
26//! struct Mascot {
27//!     name: String,
28//!     species: String,
29//!     year_of_birth: u32,
30//! }
31//!
32//! fn main() -> Result<(), Box<dyn Error>> {
33//!     let ferris = Mascot {
34//!         name: "Ferris".to_owned(),
35//!         species: "crab".to_owned(),
36//!         year_of_birth: 2015,
37//!     };
38//!
39//!     let ferris_file = File::create("ferris.cbor")?;
40//!     // Write Ferris to the given file.
41//!     // Instead of a file you can use any type that implements `io::Write`
42//!     // like a HTTP body, database connection etc.
43//!     multi_cbor::to_writer(ferris_file, &ferris)?;
44//!
45//!     // First create a Tux mascot and save it
46//!     let tux = Mascot {
47//!         name: "Tux".to_owned(),
48//!         species: "penguin".to_owned(),
49//!         year_of_birth: 1996,
50//!     };
51//!     let tux_file = File::create("tux.cbor")?;
52//!     multi_cbor::to_writer(tux_file, &tux)?;
53//!
54//!     // Now load Tux from the file.
55//!     let tux_file = File::open("tux.cbor")?;
56//!     // Serde CBOR performs roundtrip serialization meaning that
57//!     // the data will not change in any way.
58//!     let tux: Mascot = multi_cbor::from_reader(tux_file)?;
59//!
60//!     println!("{:?}", tux);
61//!     // prints: Mascot { name: "Tux", species: "penguin", year_of_birth: 1996 }
62//!
63//!     Ok(())
64//! }
65//! ```
66//!
67//! There are a lot of options available to customize the format.
68//! To operate on untyped CBOR values have a look at the `Value` type.
69//!
70//! # Type-based Serialization and Deserialization
71//! Serde provides a mechanism for low boilerplate serialization & deserialization of values to and
72//! from CBOR via the serialization API. To be able to serialize a piece of data, it must implement
73//! the `serde::Serialize` trait. To be able to deserialize a piece of data, it must implement the
74//! `serde::Deserialize` trait. Serde provides an annotation to automatically generate the
75//! code for these traits: `#[derive(Serialize, Deserialize)]`.
76//!
77//! The CBOR API also provides an enum `multi_cbor::Value`.
78//!
79//! # Packed Encoding
80//! When serializing structs or enums in CBOR the keys or enum variant names will be serialized
81//! as string keys to a map. Especially in embedded environments this can increase the file
82//! size too much. In packed encoding all struct keys, as well as any enum variant that has no data,
83//! will be serialized as variable sized integers. The first 24 entries in any struct consume only a
84//! single byte!  Packed encoding uses serde's preferred [externally tagged enum
85//! format](https://serde.rs/enum-representations.html) and therefore serializes enum variant names
86//! as string keys when that variant contains data.  So, in the packed encoding example, `FirstVariant`
87//! encodes to a single byte, but encoding `SecondVariant` requires 16 bytes.
88//!
89//! To serialize a document in this format use `Serializer::new(writer).packed_format()` or
90//! the shorthand `ser::to_vec_packed`. The deserialization works without any changes.
91//!
92//! If you would like to omit the enum variant encoding for all variants, including ones that
93//! contain data, you can add `legacy_enums()` in addition to `packed_format()`, as can seen
94//! in the Serialize using minimal encoding example.
95//!
96//! # Self describing documents
97//! In some contexts different formats are used but there is no way to declare the format used
98//! out of band. For this reason CBOR has a magic number that may be added before any document.
99//! Self describing documents are created with `serializer.self_describe()`.
100//!
101//! # Examples
102//! Read a CBOR value that is known to be a map of string keys to string values and print it.
103//!
104//! ```rust
105//! use std::collections::BTreeMap;
106//! use multi_cbor::from_slice;
107//!
108//! let slice = b"\xa5aaaAabaBacaCadaDaeaE";
109//! let value: BTreeMap<String, String> = from_slice(slice).unwrap();
110//! println!("{:?}", value); // {"e": "E", "d": "D", "a": "A", "c": "C", "b": "B"}
111//! ```
112//!
113//! Read a general CBOR value with an unknown content.
114//!
115//! ```rust
116//! use multi_cbor::from_slice;
117//! use multi_cbor::value::Value;
118//!
119//! let slice = b"\x82\x01\xa1aaab";
120//! let value: Value = from_slice(slice).unwrap();
121//! println!("{:?}", value); // Array([U64(1), Object({String("a"): String("b")})])
122//! ```
123//!
124//! Serialize an object.
125//!
126//! ```rust
127//! use std::collections::BTreeMap;
128//! use multi_cbor::to_vec;
129//!
130//! let mut programming_languages = BTreeMap::new();
131//! programming_languages.insert("rust", vec!["safe", "concurrent", "fast"]);
132//! programming_languages.insert("python", vec!["powerful", "friendly", "open"]);
133//! programming_languages.insert("js", vec!["lightweight", "interpreted", "object-oriented"]);
134//! let encoded = to_vec(&programming_languages);
135//! assert_eq!(encoded.unwrap().len(), 103);
136//! ```
137//!
138//! Deserializing data in the middle of a slice
139//! ```
140//! # extern crate multi_cbor;
141//! use multi_cbor::Deserializer;
142//!
143//! # fn main() {
144//! let data: Vec<u8> = vec![
145//!     0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x66, 0x66, 0x6f, 0x6f, 0x62,
146//!     0x61, 0x72,
147//! ];
148//! let mut deserializer = Deserializer::from_slice(&data);
149//! let value: &str = serde::de::Deserialize::deserialize(&mut deserializer)
150//!     .unwrap();
151//! let rest = &data[deserializer.byte_offset()..];
152//! assert_eq!(value, "foobar");
153//! assert_eq!(rest, &[0x66, 0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72]);
154//! # }
155//! ```
156//!
157//! Serialize using packed encoding
158//!
159//! ```rust
160//! use serde_derive::{Deserialize, Serialize};
161//! use multi_cbor::ser::to_vec_packed;
162//! use WithTwoVariants::*;
163//!
164//! #[derive(Debug, Serialize, Deserialize)]
165//! enum WithTwoVariants {
166//!     FirstVariant,
167//!     SecondVariant(u8),
168//! }
169//!
170//! let cbor = to_vec_packed(&FirstVariant).unwrap();
171//! assert_eq!(cbor.len(), 1);
172//!
173//! let cbor = to_vec_packed(&SecondVariant(0)).unwrap();
174//! assert_eq!(cbor.len(), 16); // Includes 13 bytes of "SecondVariant"
175//! ```
176//!
177//! Serialize using minimal encoding
178//!
179//! ```rust
180//! use serde_derive::{Deserialize, Serialize};
181//! use multi_cbor::{Result, Serializer, ser::{self, IoWrite}};
182//! use WithTwoVariants::*;
183//!
184//! fn to_vec_minimal<T>(value: &T) -> Result<Vec<u8>>
185//! where
186//!     T: serde::Serialize,
187//! {
188//!     let mut vec = Vec::new();
189//!     value.serialize(&mut Serializer::new(&mut IoWrite::new(&mut vec)).packed_format().legacy_enums())?;
190//!     Ok(vec)
191//! }
192//!
193//! #[derive(Debug, Serialize, Deserialize)]
194//! enum WithTwoVariants {
195//!     FirstVariant,
196//!     SecondVariant(u8),
197//! }
198//!
199//! let cbor = to_vec_minimal(&FirstVariant).unwrap();
200//! assert_eq!(cbor.len(), 1);
201//!
202//! let cbor = to_vec_minimal(&SecondVariant(0)).unwrap();
203//! assert_eq!(cbor.len(), 3);
204//! ```
205//!
206//! # `no-std` support
207//!
208//! `multi-cbor` supports building in a `no_std` context, use the following lines
209//! in your `Cargo.toml` dependencies:
210//! ``` toml
211//! [dependencies]
212//! serde = { version = "1.0", default-features = false }
213//! multi_cbor = { version = "0.1", default-features = false }
214//! ```
215//!
216//! Without the `std` feature the functions [`from_reader`], [`from_slice`], [`to_vec`], and [`to_writer`]
217//! are not exported. To export [`from_slice`] and [`to_vec`] enable the `alloc` feature. The `alloc`
218//! feature uses the [`alloc` library][alloc-lib] and requires at least version 1.36.0 of Rust.
219//!
220//! [alloc-lib]: https://doc.rust-lang.org/alloc/
221//!
222//! *Note*: to use derive macros in serde you will need to declare `serde`
223//! dependency like so:
224//! ``` toml
225//! serde = { version = "1.0", default-features = false, features = ["derive"] }
226//! ```
227//!
228//! Serialize an object with `no_std` and without `alloc`.
229//! ``` rust
230//! # #[macro_use] extern crate serde_derive;
231//! # fn main() -> Result<(), multi_cbor::Error> {
232//! use serde::Serialize;
233//! use multi_cbor::Serializer;
234//! use multi_cbor::ser::SliceWrite;
235//!
236//! #[derive(Serialize)]
237//! struct User {
238//!     user_id: u32,
239//!     password_hash: [u8; 4],
240//! }
241//!
242//! let mut buf = [0u8; 100];
243//! let writer = SliceWrite::new(&mut buf[..]);
244//! let mut ser = Serializer::new(writer);
245//! let user = User {
246//!     user_id: 42,
247//!     password_hash: [1, 2, 3, 4],
248//! };
249//! user.serialize(&mut ser)?;
250//! let writer = ser.into_inner();
251//! let size = writer.bytes_written();
252//! let expected = [
253//!     0xa2, 0x67, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x2a, 0x6d,
254//!     0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x61, 0x73,
255//!     0x68, 0x84, 0x1, 0x2, 0x3, 0x4
256//! ];
257//! assert_eq!(&buf[..size], expected);
258//! # Ok(())
259//! # }
260//! ```
261//!
262//! Deserialize an object.
263//! ``` rust
264//! # #[macro_use] extern crate serde_derive;
265//! # fn main() -> Result<(), multi_cbor::Error> {
266//! #[derive(Debug, PartialEq, Deserialize)]
267//! struct User {
268//!     user_id: u32,
269//!     password_hash: [u8; 4],
270//! }
271//!
272//! let value = [
273//!     0xa2, 0x67, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x2a, 0x6d,
274//!     0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x61, 0x73,
275//!     0x68, 0x84, 0x1, 0x2, 0x3, 0x4
276//! ];
277//!
278//! // from_slice_with_scratch will not alter input data, use it whenever you
279//! // borrow from somewhere else.
280//! // You will have to size your scratch according to the input data you
281//! // expect.
282//! use multi_cbor::de::from_slice_with_scratch;
283//! let mut scratch = [0u8; 32];
284//! let user: User = from_slice_with_scratch(&value[..], &mut scratch)?;
285//! assert_eq!(user, User {
286//!     user_id: 42,
287//!     password_hash: [1, 2, 3, 4],
288//! });
289//!
290//! let mut value = [
291//!     0xa2, 0x67, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x2a, 0x6d,
292//!     0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x61, 0x73,
293//!     0x68, 0x84, 0x1, 0x2, 0x3, 0x4
294//! ];
295//!
296//! // from_mut_slice will move data around the input slice, you may only use it
297//! // on data you may own or can modify.
298//! use multi_cbor::de::from_mut_slice;
299//! let user: User = from_mut_slice(&mut value[..])?;
300//! assert_eq!(user, User {
301//!     user_id: 42,
302//!     password_hash: [1, 2, 3, 4],
303//! });
304//! # Ok(())
305//! # }
306//! ```
307//!
308//! # Limitations
309//!
310//! While Serde CBOR strives to support all features of Serde and CBOR
311//! there are a few limitations.
312//!
313//! * [Tags] are ignored during deserialization and can't be emitted during
314//!   serialization. This is because Serde has no concept of tagged
315//!   values. See:&nbsp;[#3]
316//! * Unknown [simple values] cause an `UnassignedCode` error.
317//!   The simple values *False* and *True* are recognized and parsed as bool.
318//!   *Null* and *Undefined* are both deserialized as *unit*.
319//!   The *unit* type is serialized as *Null*. See:&nbsp;[#86]
320//! * [128-bit integers] can't be directly encoded in CBOR. If you need them
321//!   store them as a byte string. See:&nbsp;[#77]
322//!
323//! [Tags]: https://tools.ietf.org/html/rfc7049#section-2.4.4
324//! [#3]: https://github.com/pyfisch/cbor/issues/3
325//! [simple values]: https://tools.ietf.org/html/rfc7049#section-3.5
326//! [#86]: https://github.com/pyfisch/cbor/issues/86
327//! [128-bit integers]: https://doc.rust-lang.org/std/primitive.u128.html
328//! [#77]: https://github.com/pyfisch/cbor/issues/77
329
330#![deny(missing_docs)]
331#![cfg_attr(not(feature = "std"), no_std)]
332// Pedantic/nursery/cargo lints are enabled in `[lints.clippy]` in Cargo.toml.
333// The allows below suppress lints where the idiomatic CBOR encoding pattern
334// conflicts with the lint, or where fixing would require large-scale churn
335// for no behavioral benefit. Each allow has a comment explaining why.
336#![allow(
337    // `len as usize` casts are guarded by an explicit `len > usize::MAX as u64`
338    // check immediately before the cast. The serializer casts (`value as u8`
339    // etc.) are guarded by a preceding range check against the target type's
340    // MAX. These are the canonical CBOR width-encoding patterns.
341    clippy::cast_possible_truncation,
342    clippy::cast_possible_wrap,
343    // CBOR integer encoding reinterprets signed values as their unsigned
344    // bitwise representation. The sign is preserved by the major-type tag.
345    clippy::cast_sign_loss,
346    // CBOR major-type encoding uses `major << 5 | N`. The operands 24..27
347    // are RFC 7049 byte-width markers, not arbitrary decimals.
348    clippy::decimal_bitwise_operands,
349    // `DeserializerConfig` fields are all `max_*` limits by design; the
350    // prefix communicates the config category.
351    clippy::struct_field_names,
352    // `Deserializer` carries four enum-format bool flags. They are config
353    // switches, not independent state.
354    clippy::struct_excessive_bools,
355    // The `match` form is clearer than `map_or_else` at the two call sites
356    // that dispatch on `Option` from `self.next()`.
357    clippy::option_if_let_else,
358    // `Single match else` and `match_same_arms` fire on the CBOR tag and
359    // value-length dispatch tables where the match is the natural form.
360    clippy::single_match_else,
361    clippy::match_same_arms,
362    // `parse_value` and `Value::deserialize` are large `Visitor::visit_*`
363    // dispatch tables. Splitting them hurts readability.
364    clippy::too_many_lines,
365    // `missing_const_for_fn` fires on `Result`-returning helpers whose
366    // error path calls non-const constructors (`Error::syntax`,
367    // `str::from_utf8`). Clippy cannot see through those, so the suggestion
368    // is a false positive.
369    clippy::missing_const_for_fn,
370    // `missing_errors_doc` fires on internal `Result`-returning helpers
371    // whose error conditions are documented at the API boundary.
372    clippy::missing_errors_doc,
373    // `missing_panics_doc` fires on helpers that document panics in their
374    // callers; the public API documents the panics.
375    clippy::missing_panics_doc,
376)]
377
378// When we are running tests in no_std mode we need to explicitly link std, because `cargo test`
379// will not work without it.
380#[cfg(all(not(feature = "std"), test))]
381extern crate std;
382
383#[cfg(feature = "alloc")]
384extern crate alloc;
385
386pub mod config;
387pub mod de;
388pub mod error;
389mod read;
390pub mod ser;
391pub mod tags;
392mod write;
393
394#[cfg(feature = "std")]
395pub mod value;
396
397// Re-export the [items recommended by serde](https://serde.rs/conventions.html).
398#[doc(inline)]
399pub use crate::de::{Deserializer, StreamDeserializer};
400
401#[doc(inline)]
402pub use crate::error::{Error, Result};
403
404#[doc(inline)]
405pub use crate::ser::Serializer;
406
407// Convenience functions for serialization and deserialization.
408// These functions are only available in `std` mode.
409#[cfg(feature = "std")]
410#[doc(inline)]
411pub use crate::de::from_reader;
412
413#[cfg(any(feature = "std", feature = "alloc"))]
414#[doc(inline)]
415pub use crate::de::from_slice;
416
417#[cfg(any(feature = "std", feature = "alloc"))]
418#[doc(inline)]
419pub use crate::ser::to_vec;
420
421#[cfg(feature = "std")]
422#[doc(inline)]
423pub use crate::ser::to_writer;
424
425// Re-export the value type like serde_json
426#[cfg(feature = "std")]
427#[doc(inline)]
428pub use crate::value::Value;