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}