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>`, or have a method named `into` with signature `Self -> T`, where `T` is the field
122//! type. All occurences of `'_` lifetimes within `<Type>` are replaced with the trait input
123//! lifetime.
124//!
125//! When deriving `Encode` or `CborLen` and using `#[cbor({encode_,len_,}with = "<Type>")]`,
126//! `&T` must implement `Into<&<Type>>`, or have a method named `into` with signature
127//! `&Self -> &<Type>`.
128//!
129//! ```no_run
130//! use tinycbor_derive::{Encode, Decode, CborLen};
131//!
132//! #[derive(Encode, Decode, CborLen)]
133//! struct HotelRoom {
134//!     #[cbor(decode_with = "u16")]
135//!     number: u32,
136//!     #[cbor(with = "tinycbor::num::U8")]
137//!     floor: u8,
138//!     #[cbor(decode_with = "&'_ str")]
139//!     name: String,
140//!     #[cbor(with = "tinycbor::Encoded<Vec<String>>")]
141//!     amenities: Vec<String>,
142//! }
143//!
144//! ```
145//!
146//! ## `struct` codec
147//!
148//! A `struct` is encoded either as a CBOR array or a CBOR map. By default, array encoding is used
149//! and can be changed to map encoding by attaching the attribute `#[cbor(map)]` on the struct.
150//!
151//! ### Array encoding
152//!
153//! When array encoding is used, the fields are encoded within an array, in their order of
154//! declaration. The `Decode` implementation allows for both definite and indefinite length arrays,
155//! but the exact number of fields must be present in both cases. A missing or extra field leads to
156//! a decoding error.
157//!
158//! ```
159//! use tinycbor_derive::{Encode, Decode};
160//! use tinycbor::{to_vec, Decoder, Token};
161//!
162//! #[derive(Encode, Decode)]
163//! struct Account {
164//!     email: String,
165//!     username: Option<String>,
166//!     password_hash: [u8; 32],
167//! }
168//!
169//! let encoded = to_vec(
170//!     &Account {
171//!         email: "me@yahoo.com".to_string(),
172//!         username: None,
173//!         password_hash: [0u8; 32],
174//!     }
175//! );
176//! let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();
177//!
178//! assert_eq!(tokens, vec![
179//!     Token::Array(3),
180//!     Token::String("me@yahoo.com"),
181//!     Token::Null,
182//!     Token::Bytes(&[0u8; 32]),
183//! ]);
184//! ```
185//!
186//! ### Map encoding
187//!
188//! When map encoding is used, the fields are encoded as values within a map, with `u64` keys.
189//! The `Decode` implementation allows for both definite and indefinite length maps. All fields
190//! must be present in the encoded map (unless [`#[cbor(optional)]`](#cboroptional) is used),
191//! otherwise a decoding error is returned. An unknown or repeated key causes a decoding error.
192//!
193//! The key for each field is specified using the `#[n(<u64>)]` or `#[cbor(n(<u64>))]` attribute on the field.
194//!
195//! ```
196//! use tinycbor_derive::{Encode, Decode};
197//! use tinycbor::{to_vec, Decoder, Token};
198//!
199//! #[derive(Encode, Decode)]
200//! #[cbor(map)]
201//! struct Account {
202//!     #[n(0)]
203//!     email: String,
204//!     #[n(1)]
205//!     username: Option<String>,
206//!     #[n(2)]
207//!     password_hash: [u8; 32],
208//! }
209//!
210//! let encoded = to_vec(
211//!     &Account {
212//!         email: "me@yahoo.com".to_string(),
213//!         username: None,
214//!         password_hash: [0u8; 32],
215//!     }
216//! );
217//! let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();
218//!
219//! assert_eq!(tokens, vec![
220//!     Token::Map(3),
221//!     Token::Int(0u8.into()),
222//!     Token::String("me@yahoo.com"),
223//!     Token::Int(1u8.into()),
224//!     Token::Null,
225//!     Token::Int(2u8.into()),
226//!     Token::Bytes(&[0u8; 32]),
227//! ]);
228//! ```
229//!
230//! #### `#[cbor(optional)]`
231//!
232//! If a field implements `Default` and `PartialEq`, it can be marked as optional.
233//! When encoding a field marked as optional, the map entry is ommitted if the field
234//! value is equal to `Default::default()`. When decoding and if the map entry is missing, the
235//! field is initialized with `Default::default()`.
236//!
237//! ```
238//! use tinycbor_derive::{Encode, Decode};
239//! use tinycbor::{to_vec, Decoder, Token};
240//!
241//! #[derive(Encode, Decode, PartialEq)]
242//! #[cbor(map)]
243//! struct Account {
244//!    #[n(0)]
245//!    email: String,
246//!    #[cbor(n(1), optional)]
247//!    username: Option<String>,
248//!    #[n(2)]
249//!    password_hash: [u8; 32],
250//! }
251//!
252//! let encoded = to_vec(
253//!     &Account {
254//!         email: "me@yahoo.com".to_string(),
255//!         username: None,
256//!         password_hash: [0u8; 32],
257//!     }
258//! );
259//! let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();
260//!
261//! assert_eq!(tokens, vec![
262//!     Token::Map(2),
263//!     Token::Int(0u8.into()),
264//!     Token::String("me@yahoo.com"),
265//!     Token::Int(2u8.into()),
266//!     Token::Bytes(&[0u8; 32]),
267//! ]);
268//! ```
269//!
270//! __Interaction with [`#[cbor(with = "<Type>")]`](#cborencode_decode_len_with--type)__
271//!
272//! When using `with` on an optional field, the original field type's `Default` implementation is
273//! used for field initialization and encoding checks, rather than the type provided to `with`.
274//!
275//! A recurring pattern is to use `Option<T>` fields with `#[cbor(optional)]`. Note that in this
276//! case, both _"the field is absent"_ and _"the field is present with value `null`"_ are accepted
277//! when decoding. If only the former behaviour is desired (i.e., the field being `null` should
278//! error), one can use the following:
279//! ```no_run
280//! use tinycbor_derive::{Encode, Decode};
281//!
282//! #[derive(Encode, Decode)]
283//! #[cbor(map)]
284//! struct Account {
285//!     #[n(0)]
286//!     email: String,
287//!     #[cbor(n(1), decode_with = "String", optional)] // Using `decode_with = "String` prevents `null`.
288//!     username: Option<String>,
289//!     #[n(2)]
290//!     password_hash: [u8; 32],
291//! }
292//! ```
293//!
294//! ## `enum` codec
295//!
296//! An `enum` is encoded as a CBOR array. The first element of the array is the variant tag of the
297//! enum (a `u64`), followed by the encoded fields of the variant (if any).
298//!
299//! The variant tag for each variant is specified using the `#[n(<u64>)]` or `#[cbor(n(<u64>))]`
300//! attribute on the variant.
301//!
302//! ```
303//! use tinycbor_derive::{Encode, Decode};
304//! use tinycbor::{to_vec, Decoder, Token};
305//!
306//! #[derive(Encode, Decode)]
307//! enum Shape {
308//!     #[n(0)]
309//!     Dot,
310//!     #[n(1)]
311//!     Circle { radius: f64 },
312//!     #[n(2)]
313//!     Rectangle { width: f64, height: f64 },
314//! }
315//!
316//! let encoded = to_vec(&Shape::Rectangle { width: 3.0, height: 4.0 });
317//! let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();
318//!
319//! assert_eq!(tokens, vec![
320//!     Token::Array(3),
321//!     Token::Int(2u8.into()),
322//!     Token::Float(3.0),
323//!     Token::Float(4.0),
324//! ]);
325//! ```
326//!
327//! ## `#[cbor(tag_only)]`
328//!
329//! If an `enum` has no fields in any of its variants, it can be marked with the
330//! `#[cbor(tag_only)]` attribute. In this case, the enum is encoded as `u64`, rather than an
331//! array.
332//!
333//! ```
334//! use tinycbor_derive::{Encode, Decode};
335//! use tinycbor::{to_vec, Decoder, Token};
336//!
337//! #[derive(Encode, Decode)]
338//! #[cbor(tag_only)]
339//! enum Shape {
340//!     #[n(0)]
341//!     Dot,
342//!     #[n(1)]
343//!     Circle,
344//!     #[n(2)]
345//!     Rectangle,
346//! }
347//!
348//! let encoded = to_vec(&Shape::Rectangle);
349//! let tokens = Decoder(&encoded).collect::<Result<Vec<_>, _>>().unwrap();
350//!
351//! assert_eq!(tokens, vec![
352//!     Token::Int(2u8.into()),
353//! ]);
354//! ```
355
356use ast::Container;
357
358extern crate proc_macro;
359
360mod ast;
361
362/// Derive the `tinycbor::Decode` trait.
363///
364/// See the [crate] documentation for details.
365#[proc_macro_derive(Decode, attributes(n, cbor))]
366pub fn derive_decode(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
367    let input = syn::parse_macro_input!(input as syn::DeriveInput);
368    match Container::try_from(input) {
369        Ok(container) => container.decode(),
370        Err(e) => e.into_compile_error(),
371    }
372    .into()
373}
374
375/// Derive the `tinycbor::Encode` trait.
376///
377/// See the [crate] documentation for details.
378#[proc_macro_derive(Encode, attributes(n, cbor))]
379pub fn derive_encode(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
380    let input = syn::parse_macro_input!(input as syn::DeriveInput);
381    match Container::try_from(input) {
382        Ok(container) => container.encode(),
383        Err(e) => e.into_compile_error(),
384    }
385    .into()
386}
387
388/// Derive the `tinycbor::CborLen` trait.
389///
390/// See the [crate] documentation for details.
391#[proc_macro_derive(CborLen, attributes(n, cbor))]
392pub fn derive_cbor_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
393    let input = syn::parse_macro_input!(input as syn::DeriveInput);
394    match Container::try_from(input) {
395        Ok(container) => container.len(),
396        Err(e) => e.into_compile_error(),
397    }
398    .into()
399}