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