Skip to main content

tinycbor_derive/
lib.rs

1//! Procedural macros to derive [`tinycbor`](https://docs.rs/tinycbor)'s [`Encode`], [`Decode`], and [`CborLen`]
2//! traits.
3//!
4//! Deriving is supported for `struct`s and `enum`s. The encoding is cannonical whenever possible,
5//! and decoding is stricter than with [`ciborium`] or [`minicbor`].
6//!
7//!
8//! [`ciborium`]: https://docs.rs/ciborium
9//! [`minicbor`]: https://docs.rs/minicbor
10//!
11//! # Codec Specification
12//!
13//! Deriving `Encode`, `Decode` and `CborLen` is supported for `enum`s and `struct`s. This section
14//! goes over the representation of these containers, as well as how the attributes modify the
15//! behaviour of the macros.
16//!
17//! ## Array representation for `struct`s
18//!
19//! A `struct` is encoded as a CBOR array by default. The fields are encoded in their order
20//! of declaration. The `Decode` implementation allows for both definite and indefinite length
21//! arrays, but the exact number of fields must be present in both cases. A missing or extra field
22//! leads to a decoding error.
23//!
24//! ```
25//! use tinycbor_derive::{Encode, Decode};
26//! use tinycbor::{to_vec, Decoder, Token};
27//!
28//! #[derive(Encode, Decode)]
29//! struct Account {
30//!     email: String,
31//!     username: Option<String>,
32//!     password_hash: [u8; 32],
33//! }
34//!
35//! let encoded = to_vec(
36//!     &Account {
37//!         email: "me@yahoo.com".to_string(),
38//!         username: None,
39//!         password_hash: [0u8; 32],
40//!     }
41//! );
42//! let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();
43//!
44//! assert_eq!(tokens, vec![
45//!     Token::Array(3),
46//!     Token::String("me@yahoo.com"),
47//!     Token::Null,
48//!     Token::Bytes(&[0u8; 32]),
49//! ]);
50//! ```
51//!
52//! ## Map representation for `struct`s
53//!
54//! A `struct` can alternatively be encoded as a map by adding `#[cbor(map)]` on the container. The
55//! fields are encoded as values within a map, with unsigned integers as keys. The `Decode`
56//! implementation allows for both definite and indefinite length maps. All fields must be present
57//! in the encoded map (unless [`#[cbor(optional)]`](#cboroptional) is used), otherwise a decoding
58//! error is returned. An unknown or repeated key causes a decoding error.
59//!
60//! The key for each field is specified using the `#[n(<u64>)]` or `#[cbor(n(<u64>))]` attribute on
61//! the field.
62//!
63//! ```
64//! use tinycbor_derive::{Encode, Decode};
65//! use tinycbor::{to_vec, Decoder, Token};
66//!
67//! #[derive(Encode, Decode)]
68//! #[cbor(map)]
69//! struct Account {
70//!     #[n(0)]
71//!     email: String,
72//!     #[n(1)]
73//!     username: Option<String>,
74//!     #[n(2)]
75//!     password_hash: [u8; 32],
76//! }
77//!
78//! let encoded = to_vec(
79//!     &Account {
80//!         email: "me@yahoo.com".to_string(),
81//!         username: None,
82//!         password_hash: [0u8; 32],
83//!     }
84//! );
85//! let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();
86//!
87//! assert_eq!(tokens, vec![
88//!     Token::Map(3),
89//!     Token::Int(0.into()),
90//!     Token::String("me@yahoo.com"),
91//!     Token::Int(1.into()),
92//!     Token::Null,
93//!     Token::Int(2.into()),
94//!     Token::Bytes(&[0u8; 32]),
95//! ]);
96//! ```
97//!
98//! ### `#[cbor(optional)]`
99//!
100//! If a field implements `Default` and `PartialEq`, it can be marked as optional. When encoding a
101//! field marked as optional, the map entry is ommitted if the field value is equal to
102//! `Default::default()`. When decoding and the map entry is missing, the field is initialized with
103//! `Default::default()`.
104//!
105//! ```
106//! use tinycbor_derive::{Encode, Decode};
107//! use tinycbor::{to_vec, Decoder, Token};
108//!
109//! #[derive(Encode, Decode, PartialEq)]
110//! #[cbor(map)]
111//! struct Account {
112//!    #[n(0)]
113//!    email: String,
114//!    #[cbor(n(1), optional)]
115//!    username: Option<String>,
116//!    #[n(2)]
117//!    password_hash: [u8; 32],
118//! }
119//!
120//! let encoded = to_vec(
121//!     &Account {
122//!         email: "me@yahoo.com".to_string(),
123//!         username: None,
124//!         password_hash: [0u8; 32],
125//!     }
126//! );
127//! let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();
128//!
129//! assert_eq!(tokens, vec![
130//!     Token::Map(2),
131//!     Token::Int(0u8.into()),
132//!     Token::String("me@yahoo.com"),
133//!     Token::Int(2u8.into()),
134//!     Token::Bytes(&[0u8; 32]),
135//! ]);
136//! ```
137//!
138//! ## Representation for `enum`s
139//!
140//! An `enum` is encoded as a CBOR array. The first element of the array is the variant tag of the
141//! enum (a `u64`), followed by the encoded fields of the variant (if any).
142//!
143//! The variant tag for each variant is specified using the `#[n(<u64>)]` or `#[cbor(n(<u64>))]`
144//! attribute on the variant.
145//!
146//! ```
147//! use tinycbor_derive::{Encode, Decode};
148//! use tinycbor::{to_vec, Decoder, Token};
149//!
150//! #[derive(Encode, Decode)]
151//! enum Shape {
152//!     #[n(0)]
153//!     Dot,
154//!     #[n(1)]
155//!     Circle { radius: f64 },
156//!     #[n(2)]
157//!     Rectangle { width: f64, height: f64 },
158//! }
159//!
160//! let encoded = to_vec(&Shape::Rectangle { width: 3.0, height: 4.0 });
161//! let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();
162//!
163//! assert_eq!(tokens, vec![
164//!     Token::Array(3),
165//!     Token::Int(2u8.into()),
166//!     Token::Float(3.0),
167//!     Token::Float(4.0),
168//! ]);
169//! ```
170//!
171//! ## Attributes
172//!
173//! Unless otherwise noted, the attributes should only be specified on the container.
174//!
175//! ### `#[cbor(error = "<Ident>")]`
176//!
177//! This attribute gives the name of the error type to be generated when deriving `Decode`. It
178//! has no effect when deriving `Encode` or `CborLen`. By default, the error type is named
179//! `Error`.
180//!
181//! ```no_run
182//! use tinycbor_derive::{Decode};
183//!
184//! #[derive(Decode)]
185//! #[cbor(error = "PersonError")]
186//! struct Person {
187//!     name: String,
188//!     age: u16,
189//! }
190//!
191//! #[derive(Decode)]
192//! struct Company {
193//!     name: String,
194//!     ceo: Person,
195//!     employees: Vec<Person>,
196//! }
197//!
198//! type PersonErrorType = PersonError;
199//! type CompanyErrorType = Error;
200//! ```
201//!
202//! The error type generated by the macro uses associated types, which makes the generated
203//! documentation cluttered. To make it look normal, use the `-Znormalize-docs` flag on `rustdoc`.
204//!
205//! ### `#[cbor(recursive)]`
206//!
207//! This attribute is required when deriving `Decode` for a recursive type. Without it, the
208//! error type is infinitely sized, causing a compilation error.
209//!
210//! ```compile_fail
211//! use tinycbor_derive::Decode;
212//!
213//! #[derive(Decode)]
214//! enum List {
215//!     #[n(0)]
216//!     Nil,
217//!     #[n(1)]
218//!     Cons(u64, Box<List>),
219//! }
220//! ```
221//!
222//! This solves the issue by boxing the error type, and thus requires an allocator to be available.
223//!
224//! ```no_run
225//! # extern crate alloc;
226//! use tinycbor_derive::Decode;
227//!
228//! #[derive(Decode)]
229//! #[cbor(recursive)]
230//! enum List {
231//!     #[n(0)]
232//!     Nil,
233//!     #[n(1)]
234//!     Cons(u64, Box<List>),
235//! }
236//! ```
237//!
238//! ### `#[cbor({encode_,decode_,len_,}bound = "<WherePredicate>")]`
239//!
240//! Used to provide bounds on the derived implementations of `Encode`, `Decode` and `CborLen`.
241//! The `bound` statement applies to all three traits, and the others apply to their respective
242//! trait.
243//!
244//! Note that by default, the macros do not add any bounds to generic type parameters. This
245//! requires the user to add necessary bounds manually. The exception to this rule are lifetimes.
246//! For `Decode`, the lifetime attached to the trait is bound to outlive all other lifetimes in the
247//! type. This translates to:
248//! ```no_run
249//! # use tinycbor::{Decode, Decoder};
250//! # use std::{convert::Infallible, marker::PhantomData};
251//! # struct MyType<'a, 'b> { x: PhantomData<&'b &'a ()> }
252//! impl <'de, 'a, 'b, /* ... */> Decode<'de> for MyType<'a, 'b, /* ... */>
253//! where
254//!   'de: 'a + 'b + /* ... */,
255//!   // User specified bounds...
256//! {
257//!     // Implementation...
258//!     # type Error = Infallible;
259//!     # fn decode(d: &mut Decoder<'de>) -> Result<Self, Self::Error> {
260//!     #     unimplemented!()
261//!     # }
262//! }
263//! ```
264//!
265//! For `decode_bound` the predicate is allowed to use the special lifetime `'_`, which is
266//! replaced with the trait input lifetime when deriving `Decode`.
267//!
268//! ```no_run
269//! use std::borrow::Cow;
270//! use tinycbor_derive::{Encode, Decode, CborLen};
271//! use tinycbor::{Encode, Decode, CborLen};
272//!
273//! #[derive(Encode, Decode, CborLen)]
274//! #[cbor(bound = "L: std::fmt::Debug")]
275//! #[cbor(decode_bound = "L: Decode<'_>")]
276//! #[cbor(encode_bound = "L: Encode")]
277//! #[cbor(len_bound = "L: CborLen")]
278//! struct Car<'a, L> {
279//!     model: Cow<'a, str>,
280//!     kilometers: u64,
281//!     license: L,
282//! }
283//!
284//! ```
285//!
286//! ### `#[cbor(naked)]`
287//!
288//! Removes the outer CBOR array from the representation. That is, array `struct`s and `enum`s
289//! are encoded as their contents only. map `struct`s cannot be marked as naked, as they can
290//! have varying lengths.
291//!
292//! The simplest usecase is to make fieldless `enum`s encode and decode as integers instead of
293//! single-element arrays.
294//!
295//! ```no_run
296//! use tinycbor_derive::{Encode, Decode, CborLen};
297//!
298//! #[derive(Encode, Decode, CborLen)]
299//! #[cbor(naked)]
300//! enum Status {
301//!    #[n(0)]
302//!    Ok,
303//!    #[n(1)]
304//!    Error,
305//!    #[n(2)]
306//!    Sunday,
307//! }
308//! ```
309//!
310//! When the resulting representation is more than one CBOR element, the type should not be used as
311//! part of a larger structure without care. Consider the following example:
312//! ```
313//! use tinycbor_derive::Encode;
314//! use tinycbor::{Decoder, to_vec, Token};
315//!
316//! #[derive(Encode)]
317//! #[cbor(naked)]
318//! struct Danger {
319//!     value: u64,
320//!     oh_no: i64,
321//! }
322//!
323//! #[derive(Encode)]
324//! struct Careless {
325//!     value: Danger,
326//! }
327//!
328//! let cbor = to_vec(&Careless { value: Danger { value: 42, oh_no: -7 } });
329//! let tokens = Decoder(&cbor).collect::<Result<Vec<_>, _>>().unwrap();
330//!
331//! // This is probably not what you want!
332//! assert_eq!(
333//!     tokens,
334//!     vec![
335//!         Token::Array(1),
336//!         Token::Int(42.into()),
337//!         Token::Int((-7).into()),
338//!     ]
339//! );
340//! ```
341//!
342//! ### `#[cbor(tag(<u64>))]`
343//!
344//! This attribute is allowed on containers _and_ fields. It specifies a CBOR tag that wraps the
345//! encoded value.
346//!
347//! ```no_run
348//! use tinycbor_derive::{Encode, Decode, CborLen};
349//!
350//! #[derive(Encode, Decode, CborLen)]
351//! #[cbor(tag(0))]
352//! struct SavingsAccount {
353//!     unlocked: u64,
354//!     #[cbor(tag(55))]
355//!     locked: u64,
356//!     rate: f32
357//! }
358//! ```
359//!
360//! ### `#[cbor({encode_,decode_,len_,}with = "<Type>")]`
361//!
362//! This attribute is only allowed on fields. It specifies a type that provides custom `Encode`,
363//! `Decode` and/or `CborLen` implementations.
364//!
365//! When deriving `Decode` and using `#[cbor({decode_,}with = "<Type>")]`, `<Type>` must implement
366//! `Into<T>`, or have a method named `into` with signature `<Type> -> T`, where `T` is the field
367//! type. All occurences of `'_` lifetimes within `<Type>` are replaced with the trait input
368//! lifetime.
369//!
370//! When deriving `Encode` or `CborLen` and using `#[cbor({encode_,len_,}with = "<Type>")]`,
371//! `&T` must implement `Into<&<Type>>`.
372//!
373//! ```no_run
374//! use tinycbor_derive::{Encode, Decode, CborLen};
375//!
376//! #[derive(Encode, Decode, CborLen)]
377//! struct HotelRoom {
378//!     #[cbor(decode_with = "u16")]
379//!     number: u32,
380//!     #[cbor(with = "tinycbor::num::U8")]
381//!     floor: u8,
382//!     #[cbor(decode_with = "&'_ str")]
383//!     name: String,
384//!     #[cbor(with = "tinycbor::Encoded<Vec<String>>")]
385//!     amenities: Vec<String>,
386//! }
387//!
388//! ```
389//!
390//! __Interaction with [`#[cbor(optional)]`](#cboroptional)__:
391//!
392//! With `#[cbor(optional, {encode_,len_,}with = "<Type>")]`, the original field type's
393//! `Default` implementation is used for field initialization and encoding checks, rather than the type provided
394//! in `with`.
395//!
396//! A recurring pattern is to use `Option<T>` fields with `#[cbor(optional)]`. Note that in this
397//! case, both _"the field is absent"_ and _"the field is present with value `null`"_ are accepted
398//! when decoding. If only the former behaviour is desired (i.e., the field being `null` should
399//! error), one can use the following:
400//! ```no_run
401//! use tinycbor_derive::{Encode, Decode};
402//!
403//! #[derive(Encode, Decode)]
404//! #[cbor(map)]
405//! struct Account {
406//!     #[n(0)]
407//!     email: String,
408//!     #[cbor(n(1), decode_with = "String", optional)] // Using `decode_with = "String` prevents `null`.
409//!     username: Option<String>,
410//!     #[n(2)]
411//!     password_hash: [u8; 32],
412//! }
413//! ```
414
415use ast::Container;
416
417extern crate proc_macro;
418
419mod ast;
420
421/// Derive the `tinycbor::Decode` trait.
422///
423/// See the [crate] documentation for details.
424#[proc_macro_derive(Decode, attributes(n, cbor))]
425pub fn derive_decode(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
426    let input = syn::parse_macro_input!(input as syn::DeriveInput);
427    match Container::try_from(input) {
428        Ok(container) => container.decode(),
429        Err(e) => e.into_compile_error(),
430    }
431    .into()
432}
433
434/// Derive the `tinycbor::Encode` trait.
435///
436/// See the [crate] documentation for details.
437#[proc_macro_derive(Encode, attributes(n, cbor))]
438pub fn derive_encode(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
439    let input = syn::parse_macro_input!(input as syn::DeriveInput);
440    match Container::try_from(input) {
441        Ok(container) => container.encode(),
442        Err(e) => e.into_compile_error(),
443    }
444    .into()
445}
446
447/// Derive the `tinycbor::CborLen` trait.
448///
449/// See the [crate] documentation for details.
450#[proc_macro_derive(CborLen, attributes(n, cbor))]
451pub fn derive_cbor_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
452    let input = syn::parse_macro_input!(input as syn::DeriveInput);
453    match Container::try_from(input) {
454        Ok(container) => container.len(),
455        Err(e) => e.into_compile_error(),
456    }
457    .into()
458}