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({encode_,decode_,len_,}bound = "<WherePredicate>")]`
206//!
207//! Used to provide bounds on the derived implementations of `Encode`, `Decode` and `CborLen`.
208//! The `bound` statement applies to all three traits, and the others apply to their respective
209//! trait.
210//!
211//! Note that by default, the macros do not add any bounds to generic type parameters. This
212//! requires the user to add necessary bounds manually. The exception to this rule are lifetimes.
213//! For `Decode`, the lifetime attached to the trait is bound to outlive all other lifetimes in the
214//! type. This translates to:
215//! ```no_run
216//! # use tinycbor::{Decode, Decoder};
217//! # use std::{convert::Infallible, marker::PhantomData};
218//! # struct MyType<'a, 'b> { x: PhantomData<&'b &'a ()> }
219//! impl <'de, 'a, 'b, /* ... */> Decode<'de> for MyType<'a, 'b, /* ... */>
220//! where
221//! 'de: 'a + 'b + /* ... */,
222//! // User specified bounds...
223//! {
224//! // Implementation...
225//! # type Error = Infallible;
226//! # fn decode(d: &mut Decoder<'de>) -> Result<Self, Self::Error> {
227//! # unimplemented!()
228//! # }
229//! }
230//! ```
231//!
232//! For `decode_bound` the predicate is allowed to use the special lifetime `'_`, which is
233//! replaced with the trait input lifetime when deriving `Decode`.
234//!
235//! ```no_run
236//! use std::borrow::Cow;
237//! use tinycbor_derive::{Encode, Decode, CborLen};
238//! use tinycbor::{Encode, Decode, CborLen};
239//!
240//! #[derive(Encode, Decode, CborLen)]
241//! #[cbor(bound = "L: std::fmt::Debug")]
242//! #[cbor(decode_bound = "L: Decode<'_>")]
243//! #[cbor(encode_bound = "L: Encode")]
244//! #[cbor(len_bound = "L: CborLen")]
245//! struct Car<'a, L> {
246//! model: Cow<'a, str>,
247//! kilometers: u64,
248//! license: L,
249//! }
250//!
251//! ```
252//!
253//! ### `#[cbor(naked)]`
254//!
255//! Removes the outer CBOR array from the representation. That is, array `struct`s and `enum`s
256//! are encoded as their contents only. map `struct`s cannot be marked as naked, as they can
257//! have varying lengths.
258//!
259//! The simplest usecase is to make fieldless `enum`s encode and decode as integers instead of
260//! single-element arrays.
261//!
262//! ```no_run
263//! use tinycbor_derive::{Encode, Decode, CborLen};
264//!
265//! #[derive(Encode, Decode, CborLen)]
266//! #[cbor(naked)]
267//! enum Status {
268//! #[n(0)]
269//! Ok,
270//! #[n(1)]
271//! Error,
272//! #[n(2)]
273//! Sunday,
274//! }
275//! ```
276//!
277//! When the resulting representation is more than one CBOR element, the type should not be used as
278//! part of a larger structure without care. Consider the following example:
279//! ```
280//! use tinycbor_derive::Encode;
281//! use tinycbor::{Decoder, to_vec, Token};
282//!
283//! #[derive(Encode)]
284//! #[cbor(naked)]
285//! struct Danger {
286//! value: u64,
287//! oh_no: i64,
288//! }
289//!
290//! #[derive(Encode)]
291//! struct Careless {
292//! value: Danger,
293//! }
294//!
295//! let cbor = to_vec(&Careless { value: Danger { value: 42, oh_no: -7 } });
296//! let tokens = Decoder(&cbor).collect::<Result<Vec<_>, _>>().unwrap();
297//!
298//! // This is probably not what you want!
299//! assert_eq!(
300//! tokens,
301//! vec![
302//! Token::Array(1),
303//! Token::Int(42.into()),
304//! Token::Int((-7).into()),
305//! ]
306//! );
307//! ```
308//!
309//! ### `#[cbor(tag(<u64>))]`
310//!
311//! This attribute is allowed on containers _and_ fields. It specifies a CBOR tag that wraps the
312//! encoded value.
313//!
314//! ```no_run
315//! use tinycbor_derive::{Encode, Decode, CborLen};
316//!
317//! #[derive(Encode, Decode, CborLen)]
318//! #[cbor(tag(0))]
319//! struct SavingsAccount {
320//! unlocked: u64,
321//! #[cbor(tag(55))]
322//! locked: u64,
323//! rate: f32
324//! }
325//! ```
326//!
327//! ### `#[cbor({encode_,decode_,len_,}with = "<Type>")]`
328//!
329//! This attribute is only allowed on fields. It specifies a type that provides custom `Encode`,
330//! `Decode` and/or `CborLen` implementations.
331//!
332//! When deriving `Decode` and using `#[cbor({decode_,}with = "<Type>")]`, `<Type>` must implement
333//! `Into<T>`, or have a method named `into` with signature `<Type> -> T`, where `T` is the field
334//! type. All occurences of `'_` lifetimes within `<Type>` are replaced with the trait input
335//! lifetime.
336//!
337//! When deriving `Encode` or `CborLen` and using `#[cbor({encode_,len_,}with = "<Type>")]`,
338//! `&T` must implement `Into<&<Type>>`.
339//!
340//! ```no_run
341//! use tinycbor_derive::{Encode, Decode, CborLen};
342//!
343//! #[derive(Encode, Decode, CborLen)]
344//! struct HotelRoom {
345//! #[cbor(decode_with = "u16")]
346//! number: u32,
347//! #[cbor(with = "tinycbor::num::U8")]
348//! floor: u8,
349//! #[cbor(decode_with = "&'_ str")]
350//! name: String,
351//! #[cbor(with = "tinycbor::Encoded<Vec<String>>")]
352//! amenities: Vec<String>,
353//! }
354//!
355//! ```
356//!
357//! __Interaction with [`#[cbor(optional)]`](#cboroptional)__:
358//!
359//! With `#[cbor(optional, {encode_,len_,}with = "<Type>")]`, the original field type's
360//! `Default` implementation is used for field initialization and encoding checks, rather than the type provided
361//! in `with`.
362//!
363//! A recurring pattern is to use `Option<T>` fields with `#[cbor(optional)]`. Note that in this
364//! case, both _"the field is absent"_ and _"the field is present with value `null`"_ are accepted
365//! when decoding. If only the former behaviour is desired (i.e., the field being `null` should
366//! error), one can use the following:
367//! ```no_run
368//! use tinycbor_derive::{Encode, Decode};
369//!
370//! #[derive(Encode, Decode)]
371//! #[cbor(map)]
372//! struct Account {
373//! #[n(0)]
374//! email: String,
375//! #[cbor(n(1), decode_with = "String", optional)] // Using `decode_with = "String` prevents `null`.
376//! username: Option<String>,
377//! #[n(2)]
378//! password_hash: [u8; 32],
379//! }
380//! ```
381
382use ast::Container;
383
384extern crate proc_macro;
385
386mod ast;
387
388/// Derive the `tinycbor::Decode` trait.
389///
390/// See the [crate] documentation for details.
391#[proc_macro_derive(Decode, attributes(n, cbor))]
392pub fn derive_decode(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.decode(),
396 Err(e) => e.into_compile_error(),
397 }
398 .into()
399}
400
401/// Derive the `tinycbor::Encode` trait.
402///
403/// See the [crate] documentation for details.
404#[proc_macro_derive(Encode, attributes(n, cbor))]
405pub fn derive_encode(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
406 let input = syn::parse_macro_input!(input as syn::DeriveInput);
407 match Container::try_from(input) {
408 Ok(container) => container.encode(),
409 Err(e) => e.into_compile_error(),
410 }
411 .into()
412}
413
414/// Derive the `tinycbor::CborLen` trait.
415///
416/// See the [crate] documentation for details.
417#[proc_macro_derive(CborLen, attributes(n, cbor))]
418pub fn derive_cbor_len(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
419 let input = syn::parse_macro_input!(input as syn::DeriveInput);
420 match Container::try_from(input) {
421 Ok(container) => container.len(),
422 Err(e) => e.into_compile_error(),
423 }
424 .into()
425}