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//! `#[derive(Decode)]` may not generate an error type. Those cases are:
182//! - Fieldless structs.
183//! - `naked` structs with a single field.
184//! - Variantless enums.
185//! - `naked` C-like enums.
186//!
187//! In those cases, this attribute has no effect.
188//!
189//! ```no_run
190//! use tinycbor_derive::{Decode};
191//!
192//! #[derive(Decode)]
193//! #[cbor(error = "PersonError")]
194//! struct Person {
195//! name: String,
196//! age: u16,
197//! }
198//!
199//! #[derive(Decode)]
200//! struct Company {
201//! name: String,
202//! ceo: Person,
203//! employees: Vec<Person>,
204//! }
205//!
206//! type PersonErrorType = PersonError;
207//! type CompanyErrorType = Error;
208//! ```
209//!
210//! The error type generated by the macro uses associated types, which makes the generated
211//! documentation cluttered. To make it look normal, use the `-Znormalize-docs` flag on `rustdoc`.
212//!
213//! ### `#[cbor(recursive)]`
214//!
215//! This attribute is required when deriving `Decode` for a recursive type. Without it, the
216//! error type is infinitely sized, causing a compilation error.
217//!
218//! ```compile_fail
219//! use tinycbor_derive::Decode;
220//!
221//! #[derive(Decode)]
222//! enum List {
223//! #[n(0)]
224//! Nil,
225//! #[n(1)]
226//! Cons(u64, Box<List>),
227//! }
228//! ```
229//!
230//! This solves the issue by boxing the error type, and thus requires an allocator to be available.
231//!
232//! ```no_run
233//! # extern crate alloc;
234//! use tinycbor_derive::Decode;
235//!
236//! #[derive(Decode)]
237//! #[cbor(recursive)]
238//! enum List {
239//! #[n(0)]
240//! Nil,
241//! #[n(1)]
242//! Cons(u64, Box<List>),
243//! }
244//! ```
245//!
246//! ### `#[cbor({encode_,decode_,len_,}bound = "<WherePredicate>")]`
247//!
248//! Used to provide bounds on the derived implementations of `Encode`, `Decode` and `CborLen`.
249//! The `bound` statement applies to all three traits, and the others apply to their respective
250//! trait.
251//!
252//! Note that by default, the macros do not add any bounds to generic type parameters. This
253//! requires the user to add necessary bounds manually. The exception to this rule are lifetimes.
254//! For `Decode`, the lifetime attached to the trait is bound to outlive all other lifetimes in the
255//! type. This translates to:
256//! ```no_run
257//! # use tinycbor::{Decode, Decoder};
258//! # use std::{convert::Infallible, marker::PhantomData};
259//! # struct MyType<'a, 'b> { x: PhantomData<&'b &'a ()> }
260//! impl <'de, 'a, 'b, /* ... */> Decode<'de> for MyType<'a, 'b, /* ... */>
261//! where
262//! 'de: 'a + 'b + /* ... */,
263//! // User specified bounds...
264//! {
265//! // Implementation...
266//! # type Error = Infallible;
267//! # fn decode(d: &mut Decoder<'de>) -> Result<Self, Self::Error> {
268//! # unimplemented!()
269//! # }
270//! }
271//! ```
272//!
273//! For `decode_bound` the predicate is allowed to use the special lifetime `'_`, which is
274//! replaced with the trait input lifetime when deriving `Decode`.
275//!
276//! ```no_run
277//! use std::borrow::Cow;
278//! use tinycbor_derive::{Encode, Decode, CborLen};
279//! use tinycbor::{Encode, Decode, CborLen};
280//!
281//! #[derive(Encode, Decode, CborLen)]
282//! #[cbor(bound = "L: std::fmt::Debug")]
283//! #[cbor(decode_bound = "L: Decode<'_>")]
284//! #[cbor(encode_bound = "L: Encode")]
285//! #[cbor(len_bound = "L: CborLen")]
286//! struct Car<'a, L> {
287//! model: Cow<'a, str>,
288//! kilometers: u64,
289//! license: L,
290//! }
291//!
292//! ```
293//!
294//! ### `#[cbor(naked)]`
295//!
296//! Removes the outer CBOR array from the representation. That is, array `struct`s and `enum`s
297//! are encoded as their contents only. map `struct`s cannot be marked as naked, as they can
298//! have varying lengths.
299//!
300//! The simplest usecase is to make fieldless `enum`s encode and decode as integers instead of
301//! single-element arrays.
302//!
303//! ```no_run
304//! use tinycbor_derive::{Encode, Decode, CborLen};
305//!
306//! #[derive(Encode, Decode, CborLen)]
307//! #[cbor(naked)]
308//! enum Status {
309//! #[n(0)]
310//! Ok,
311//! #[n(1)]
312//! Error,
313//! #[n(2)]
314//! Sunday,
315//! }
316//! ```
317//!
318//! Another usecase is to create newtype wrappers that do not add any CBOR structure around the
319//! inner type.
320//!
321//! ```no_run
322//! use tinycbor_derive::{Encode, Decode, CborLen};
323//!
324//! #[derive(Encode, Decode, CborLen)]
325//! #[cbor(naked)]
326//! struct UserId(u64);
327//! ```
328//!
329//! When the resulting representation is more than one CBOR element, the type should not be used as
330//! part of a larger structure without care. Consider the following example:
331//! ```
332//! use tinycbor_derive::Encode;
333//! use tinycbor::{Decoder, to_vec, Token};
334//!
335//! #[derive(Encode)]
336//! #[cbor(naked)]
337//! struct Danger {
338//! value: u64,
339//! oh_no: i64,
340//! }
341//!
342//! #[derive(Encode)]
343//! struct Careless {
344//! value: Danger,
345//! }
346//!
347//! let cbor = to_vec(&Careless { value: Danger { value: 42, oh_no: -7 } });
348//! let tokens = Decoder(&cbor).collect::<Result<Vec<_>, _>>().unwrap();
349//!
350//! // This is probably not what you want!
351//! assert_eq!(
352//! tokens,
353//! vec![
354//! Token::Array(1),
355//! Token::Int(42.into()),
356//! Token::Int((-7).into()),
357//! ]
358//! );
359//! ```
360//!
361//! ### `#[cbor(tag(<u64>))]`
362//!
363//! This attribute is allowed on containers _and_ fields. It specifies a CBOR tag that wraps the
364//! encoded value.
365//!
366//! ```no_run
367//! use tinycbor_derive::{Encode, Decode, CborLen};
368//!
369//! #[derive(Encode, Decode, CborLen)]
370//! #[cbor(tag(0))]
371//! struct SavingsAccount {
372//! unlocked: u64,
373//! #[cbor(tag(55))]
374//! locked: u64,
375//! rate: f32
376//! }
377//! ```
378//!
379//! ### `#[cbor({encode_,decode_,len_,}with = "<Type>")]`
380//!
381//! This attribute is only allowed on fields. It specifies a type that provides custom `Encode`,
382//! `Decode` and/or `CborLen` implementations.
383//!
384//! When deriving `Decode` and using `#[cbor({decode_,}with = "<Type>")]`, `<Type>` must implement
385//! `Into<T>`, or have a method named `into` with signature `<Type> -> T`, where `T` is the field
386//! type. All occurences of `'_` lifetimes within `<Type>` are replaced with the trait input
387//! lifetime.
388//!
389//! When deriving `Encode` or `CborLen` and using `#[cbor({encode_,len_,}with = "<Type>")]`,
390//! `&T` must implement `Into<&<Type>>`.
391//!
392//! ```no_run
393//! use tinycbor_derive::{Encode, Decode, CborLen};
394//!
395//! #[derive(Encode, Decode, CborLen)]
396//! struct HotelRoom {
397//! #[cbor(decode_with = "u16")]
398//! number: u32,
399//! #[cbor(with = "tinycbor::num::U8")]
400//! floor: u8,
401//! #[cbor(decode_with = "&'_ str")]
402//! name: String,
403//! #[cbor(with = "tinycbor::Encoded<Vec<String>>")]
404//! amenities: Vec<String>,
405//! }
406//!
407//! ```
408//!
409//! __Interaction with [`#[cbor(optional)]`](#cboroptional)__:
410//!
411//! With `#[cbor(optional, {encode_,len_,}with = "<Type>")]`, the original field type's
412//! `Default` implementation is used for field initialization and encoding checks, rather than the type provided
413//! in `with`.
414//!
415//! A recurring pattern is to use `Option<T>` fields with `#[cbor(optional)]`. Note that in this
416//! case, both _"the field is absent"_ and _"the field is present with value `null`"_ are accepted
417//! when decoding. If only the former behaviour is desired (i.e., the field being `null` should
418//! error), one can use the following:
419//! ```no_run
420//! use tinycbor_derive::{Encode, Decode};
421//!
422//! #[derive(Encode, Decode)]
423//! #[cbor(map)]
424//! struct Account {
425//! #[n(0)]
426//! email: String,
427//! #[cbor(n(1), decode_with = "String", optional)] // Using `decode_with = "String` prevents `null`.
428//! username: Option<String>,
429//! #[n(2)]
430//! password_hash: [u8; 32],
431//! }
432//! ```
433
434use ast::Container;
435
436extern crate proc_macro;
437
438mod ast;
439
440/// Derive the `tinycbor::Decode` trait.
441///
442/// See the [crate] documentation for details.
443#[proc_macro_derive(Decode, attributes(n, cbor))]
444pub fn derive_decode(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
445 let input = syn::parse_macro_input!(input as syn::DeriveInput);
446 match Container::try_from(input) {
447 Ok(container) => container.decode(),
448 Err(e) => e.into_compile_error(),
449 }
450 .into()
451}
452
453/// Derive the `tinycbor::Encode` trait.
454///
455/// See the [crate] documentation for details.
456#[proc_macro_derive(Encode, attributes(n, cbor))]
457pub fn derive_encode(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
458 let input = syn::parse_macro_input!(input as syn::DeriveInput);
459 match Container::try_from(input) {
460 Ok(container) => container.encode(),
461 Err(e) => e.into_compile_error(),
462 }
463 .into()
464}
465
466/// Derive the `tinycbor::CborLen` trait.
467///
468/// See the [crate] documentation for details.
469#[proc_macro_derive(CborLen, attributes(n, cbor))]
470pub fn derive_cbor_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
471 let input = syn::parse_macro_input!(input as syn::DeriveInput);
472 match Container::try_from(input) {
473 Ok(container) => container.len(),
474 Err(e) => e.into_compile_error(),
475 }
476 .into()
477}