Crate tinycbor_derive

Crate tinycbor_derive 

Source
Expand description

Procedural macros to derive tinycbor’s Encode, Decode, and CborLen traits.

Deriving is supported for structs and enums. The encoding is cannonical whenever possible, and decoding is stricter than with ciborium or minicbor.

§Codec Specification

Deriving Encode, Decode and CborLen is supported for enums and structs. The following attributes are allowed on both container kinds.

§#[cbor(error = "<Ident>")]

This attribute gives the name of the error type to be generated when deriving Decode. It has no effect when deriving Encode or CborLen. By default, the error type is named Error.

use tinycbor_derive::{Decode};

#[derive(Decode)]
#[cbor(error = "PersonError")]
struct Person {
    name: String,
    age: u16,
}

#[derive(Decode)]
struct Company {
    name: String,
    ceo: Person,
    employees: Vec<Person>,
}

type PersonErrorType = PersonError;
type CompanyErrorType = Error;

The error type generated by the macro uses associated types, which makes the generated documentation cluttered. To make it look normal, use the -Znormalize-docs flag on rustdoc.

§#[cbor({encode_,decode_,len_,}bound = "<WherePredicate>")]

Used to provide bounds on the derived implementations of Encode, Decode and CborLen. The bound statement applies to all three traits, and the others apply to their respective trait.

Note that by default, the macros do not add any bounds to generic type parameters. This requires the user to add necessary bounds manually. The exception to this rule are lifetimes. For Decode, the lifetime attached to the trait is bound to outlive all other lifetimes in the type. This translates to:

impl <'de, 'a, 'b, /* ... */> Decode<'de> for MyType<'a, 'b, /* ... */>
where
  'de: 'a + 'b + /* ... */,
  // User specified bounds...
{
    // Implementation...
}

For decode_bound the predicate is allowed to use the special lifetime '_, which is replaced with the trait input lifetime when deriving Decode.

use std::borrow::Cow;
use tinycbor_derive::{Encode, Decode, CborLen};
use tinycbor::{Encode, Decode, CborLen};

#[derive(Encode, Decode, CborLen)]
#[cbor(bound = "L: std::fmt::Debug")]
#[cbor(decode_bound = "L: Decode<'_>")]
#[cbor(encode_bound = "L: Encode")]
#[cbor(len_bound = "L: CborLen")]
struct Car<'a, L> {
    model: Cow<'a, str>,
    kilometers: u64,
    license: L,
}

§#[cbor(tag(<u64>))]

This attribute is allowed on containers and fields. It specifies a CBOR tag that wraps the encoded value.

use tinycbor_derive::{Encode, Decode, CborLen};

#[derive(Encode, Decode, CborLen)]
#[cbor(tag(0))]
struct SavingsAccount {
    unlocked: u64,
    #[cbor(tag(55))]
    locked: u64,
    rate: f32
}

§Field attributes

The following attributes are allowed on fields of structs and enum variants.

§#[cbor({encode_,decode_,len_,}with = "<Type>")]

Specifies a type that provides custom Encode, Decode and/or CborLen implementation.

When deriving Decode and using #[cbor({decode_,}with = "<Type>")], <Type> must implement Into<T> where T is the field type. All occurences of '_ lifetimes within <Type> are replaced with the trait input lifetime.

When deriving Encode or CborLen and using #[cbor({encode_,len_,}with = "<Type>")], T must implement AsRef<<Type>> where T is the field type.

use tinycbor_derive::{Encode, Decode, CborLen};

#[derive(Encode, Decode, CborLen)]
struct HotelRoom {
    #[cbor(decode_with = "u16")]
    number: u32,
    #[cbor(with = "tinycbor::num::U8")]
    floor: u8,
    #[cbor(decode_with = "&'_ str")]
    name: String,
}

§struct codec

A struct is encoded either as a CBOR array or a CBOR map. By default, array encoding is used and can be changed to map encoding by attaching the attribute #[cbor(map)] on the struct.

§Array encoding

When array encoding is used, the fields are encoded within an array, in their order of declaration. The Decode implementation allows for both definite and indefinite length arrays, but the exact number of fields must be present in both cases. A missing or extra field leads to a decoding error.

use tinycbor_derive::{Encode, Decode};
use tinycbor::{to_vec, Decoder, Token};

#[derive(Encode, Decode)]
struct Account {
    email: String,
    username: Option<String>,
    password_hash: [u8; 32],
}

let encoded = to_vec(
    &Account {
        email: "me@yahoo.com".to_string(),
        username: None,
        password_hash: [0u8; 32],
    }
);
let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();

assert_eq!(tokens, vec![
    Token::Array(3),
    Token::String("me@yahoo.com"),
    Token::Null,
    Token::Bytes(&[0u8; 32]),
]);

§Map encoding

When map encoding is used, the fields are encoded as values within a map, with u64 keys. The Decode implementation allows for both definite and indefinite length maps. All fields must be present in the encoded map (unless #[cbor(optional)] is used), otherwise a decoding error is returned. An unknown or repeated key causes a decoding error.

The key for each field is specified using the #[n(<u64>)] or #[cbor(n(<u64>))] attribute on the field.

use tinycbor_derive::{Encode, Decode};
use tinycbor::{to_vec, Decoder, Token};

#[derive(Encode, Decode)]
#[cbor(map)]
struct Account {
    #[n(0)]
    email: String,
    #[n(1)]
    username: Option<String>,
    #[n(2)]
    password_hash: [u8; 32],
}

let encoded = to_vec(
    &Account {
        email: "me@yahoo.com".to_string(),
        username: None,
        password_hash: [0u8; 32],
    }
);
let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();

assert_eq!(tokens, vec![
    Token::Map(3),
    Token::Int(0u8.into()),
    Token::String("me@yahoo.com"),
    Token::Int(1u8.into()),
    Token::Null,
    Token::Int(2u8.into()),
    Token::Bytes(&[0u8; 32]),
]);
§#[cbor(optional)]

If a field implements Default and PartialEq, it can be marked as optional. When encoding a field marked as optional, the map entry is ommitted if the field value is equal to Default::default(). When decoding and if the map entry is missing, the field is initialized with Default::default().

use tinycbor_derive::{Encode, Decode};
use tinycbor::{to_vec, Decoder, Token};

#[derive(Encode, Decode, PartialEq)]
#[cbor(map)]
struct Account {
   #[n(0)]
   email: String,
   #[cbor(n(1), optional)]
   username: Option<String>,
   #[n(2)]
   password_hash: [u8; 32],
}

let encoded = to_vec(
    &Account {
        email: "me@yahoo.com".to_string(),
        username: None,
        password_hash: [0u8; 32],
    }
);
let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();

assert_eq!(tokens, vec![
    Token::Map(2),
    Token::Int(0u8.into()),
    Token::String("me@yahoo.com"),
    Token::Int(2u8.into()),
    Token::Bytes(&[0u8; 32]),
]);

Interaction with #[cbor(with = "<Type>")]

When using with on an optional field, the original field type’s Default implementation is used for field initialization and encoding checks, rather than the type provided to with.

A recurring pattern is to use Option<T> fields with #[cbor(optional)]. Note that in this case, both “the field is absent” and “the field is present with value null are accepted when decoding. If only the former behaviour is desired (i.e., the field being null should error), one can use the following:

use tinycbor_derive::{Encode, Decode};

#[derive(Encode, Decode)]
#[cbor(map)]
struct Account {
    #[n(0)]
    email: String,
    #[cbor(n(1), decode_with = "String", optional)] // Using `decode_with = "String` prevents `null`.
    username: Option<String>,
    #[n(2)]
    password_hash: [u8; 32],
}

§enum codec

An enum is encoded as a CBOR array. The first element of the array is the variant tag of the enum (a u64), followed by the encoded fields of the variant (if any).

The variant tag for each variant is specified using the #[n(<u64>)] or #[cbor(n(<u64>))] attribute on the variant.

use tinycbor_derive::{Encode, Decode};
use tinycbor::{to_vec, Decoder, Token};

#[derive(Encode, Decode)]
enum Shape {
    #[n(0)]
    Dot,
    #[n(1)]
    Circle { radius: f64 },
    #[n(2)]
    Rectangle { width: f64, height: f64 },
}

let encoded = to_vec(&Shape::Rectangle { width: 3.0, height: 4.0 });
let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();

assert_eq!(tokens, vec![
    Token::Array(3),
    Token::Int(2u8.into()),
    Token::Float(3.0),
    Token::Float(4.0),
]);

§#[cbor(tag_only)]

If an enum has no fields in any of its variants, it can be marked with the #[cbor(tag_only)] attribute. In this case, the enum is encoded as u64, rather than an array.

use tinycbor_derive::{Encode, Decode};
use tinycbor::{to_vec, Decoder, Token};

#[derive(Encode, Decode)]
#[cbor(tag_only)]
enum Shape {
    #[n(0)]
    Dot,
    #[n(1)]
    Circle,
    #[n(2)]
    Rectangle,
}

let encoded = to_vec(&Shape::Rectangle);
let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();

assert_eq!(tokens, vec![
    Token::Int(2u8.into()),
]);

Derive Macros§

CborLen
Derive the tinycbor::CborLen trait.
Decode
Derive the tinycbor::Decode trait.
Encode
Derive the tinycbor::Encode trait.