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}