multi-cbor 0.1.0

CBOR support for serde with enhanced error handling and DAG-CBOR tags support.
Documentation

Build Status License Crates.io Documentation

multi-cbor

CBOR serialization and deserialization for serde, forked from the archived upstream serde_cbor crate and extended with a tags feature for DAG-CBOR tag round-tripping.

multi-cbor is a maintained drop-in replacement for serde_cbor. Upstream serde_cbor has been unmaintained and archived since 2021. The crate name on crates.io is still owned by the archived upstream, so this fork publishes under the multi-cbor name. The license stays MIT OR Apache-2.0, the same as upstream.

The tags feature is the reason this fork exists. Upstream ignored CBOR tags during deserialization and refused to emit them during serialization. The tags feature adds the thread-local tag plumbing that DAG-CBOR needs.

Table of Contents

Features

  • CBOR serialization and deserialization for any serde-compatible type.
  • Packed encoding. Struct keys and unit enum variants encode as integers.
  • Self-describing documents. The CBOR magic number can be prepended.
  • The Value enum for untyped CBOR data, like serde_json::Value.
  • DAG-CBOR tag round-tripping under the tags feature.
  • no_std support with an alloc mode.
  • Zero unsafe code.

Install

Add this to your Cargo.toml:

[dependencies]
multi-cbor = "0.1"

For no_std builds:

[dependencies]
multi-cbor = { version = "0.1", default-features = false }
# Enable alloc to get from_slice and to_vec without std:
# multi-cbor = { version = "0.1", default-features = false, features = ["alloc"] }

MSRV: Rust 1.85 (Edition 2021).

Usage

use serde_derive::{Deserialize, Serialize};
use multi_cbor::{from_slice, to_vec};

#[derive(Debug, Serialize, Deserialize, PartialEq)]
struct Mascot {
    name: String,
    species: String,
    year_of_birth: u32,
}

let ferris = Mascot {
    name: "Ferris".to_owned(),
    species: "crab".to_owned(),
    year_of_birth: 2015,
};

let bytes = to_vec(&ferris).unwrap();
let back: Mascot = from_slice(&bytes).unwrap();
assert_eq!(ferris, back);

For streaming I/O, use to_writer and from_reader (these require the std feature, which is on by default):

use std::fs::File;
use multi_cbor::{to_writer, from_reader};

# use serde_derive::{Deserialize, Serialize};
# #[derive(Debug, Serialize, Deserialize)]
# struct Mascot { name: String, species: String, year_of_birth: u32 }
# fn main() -> Result<(), Box<dyn std::error::Error>> {
let mascot = Mascot {
    name: "Tux".to_owned(),
    species: "penguin".to_owned(),
    year_of_birth: 1996,
};
to_writer(File::create("tux.cbor")?, &mascot)?;

let file = File::open("tux.cbor")?;
let back: Mascot = from_reader(file)?;
assert_eq!(mascot.name, back.name);
# Ok(())
# }

The tags Feature

CBOR tags mark a data item with a tag number that tells the consumer how to interpret the enclosed value. The DAG-CBOR subset of CBOR uses tags for the Cid type (tag 42) and the Link wrapper (tag 42 on a byte string).

Upstream serde_cbor ignored tags on read and refused to write them. This fork adds the tags cargo feature. When the feature is on:

  • Tagged<T> serializes a (Option<u64>, T) pair, writing the tag before the value.
  • current_cbor_tag() returns the tag that is in scope during a visit_newtype_struct call. This lets a Deserialize implementation read the tag and dispatch on it.
  • A thread-local TagGuard records and restores the active tag during nested serialization.

When the feature is off, the same API compiles, but current_cbor_tag() always returns None and tags are not written. This keeps the no-tags build byte-compatible with upstream serde_cbor.

[dependencies]
multi-cbor = { version = "0.1", features = ["tags"] }
use multi_cbor::tags::Tagged;
use multi_cbor::{from_slice, to_vec};

let value = Tagged::new(Some(42), b"hello".to_vec());
let bytes = to_vec(&value).unwrap();
let back: Tagged<Vec<u8>> = from_slice(&bytes).unwrap();
assert_eq!(value.tag, back.tag);
assert_eq!(value.value, back.value);

Feature Flags

Feature Default Effect
std yes Enables from_reader, to_writer, and the Value module. Requires serde/std.
alloc no Enables from_slice and to_vec for no_std + alloc builds. Requires serde/alloc.
tags no Enables CBOR tag round-tripping for DAG-CBOR. Requires the std feature (uses thread_local!). See The tags Feature.
unsealed_read_write no Exposes the read::Read and write::Write traits so external callers can build custom sources and sinks.

no_std Support

With default-features = false the crate builds without std. Only the Serializer and Deserializer types and the SliceWrite and SliceRead helpers are available. Enable alloc to get from_slice and to_vec. The Value module and from_reader / to_writer require std.

[dependencies]
multi-cbor = { version = "0.1", default-features = false, features = ["alloc"] }
serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] }

Fork Notes

This crate is a fork of the archived upstream serde_cbor 0.7.0. The differences from upstream are:

  • The crate is renamed from serde_cbor to multi-cbor.
  • The tags cargo feature is added for DAG-CBOR tag round-tripping.
  • The repository field points at the cryptidtech/multi-cbor GitHub repo.
  • The MSRV is declared as 1.85.
  • The dev-dependencies that referenced the bs-* BetterSign workspace crates are removed. The cid_linked_list example is removed for the same reason. A later release will re-point it at the standalone multi-cid crate.

The license stays MIT OR Apache-2.0, the same as upstream.

Testing

cargo test --all-features
cargo clippy --all-targets --all-features -- -D warnings
cargo doc --all-features

Maintainers

Contribute

Pull requests go to the cryptidtech/multi-cbor repository. Sign commits with GPG. Use Conventional Commits messages.

License

Dual-licensed as MIT OR Apache-2.0, the same as upstream serde_cbor.

See LICENSE-MIT and LICENSE-APACHE for the full text.