simple_tlv_derive/
lib.rs

1//! Custom derive support for the `simple-tlv` crate
2//!
3//! With `#[tlv(slice)]` set, `Encodable` should work for fields implementing `AsRef<[u8]>`,
4//! and `Decodable` should work for fields implementing `TryFrom<[u8]>`, even if the field
5//! is not `Decodable` or `Encodable`.
6
7#![crate_type = "proc-macro"]
8#![warn(rust_2018_idioms, trivial_casts, unused_qualifications)]
9
10mod decodable;
11use decodable::DeriveDecodableStruct;
12mod encodable;
13use encodable::DeriveEncodableStruct;
14
15
16use proc_macro2::TokenStream;
17use syn::{
18    Attribute, Field, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta,
19};
20use synstructure::{decl_derive, Structure};
21
22decl_derive!(
23    [Decodable, attributes(tlv)] =>
24
25    /// Derive the [`Decodable`][1] trait on a struct.
26    ///
27    /// See [toplevel documentation for the `simple-tlv_derive` crate][2] for more
28    /// information about how to use this macro.
29    ///
30    /// [1]: https://docs.rs/simple-tlv/latest/simple_tlv/trait.Decodable.html
31    /// [2]: https://docs.rs/simple-tlv_derive/
32    derive_decodable
33);
34
35decl_derive!(
36    [Encodable, attributes(tlv)] =>
37
38    /// Derive the [`Encodable`][1] trait on a struct.
39    ///
40    /// See [toplevel documentation for the `simple-tlv_derive` crate][2] for more
41    /// information about how to use this macro.
42    ///
43    /// [1]: https://docs.rs/simple-tlv/latest/simple_tlv/trait.Decodable.html
44    /// [2]: https://docs.rs/simple-tlv_derive/
45    derive_encodable
46);
47
48/// Custom derive for `simple_tlv::Decodable`
49fn derive_decodable(s: Structure<'_>) -> TokenStream {
50    let ast = s.ast();
51
52    // TODO: enum support
53    match &ast.data {
54        syn::Data::Struct(data) => DeriveDecodableStruct::derive(s, data, &ast.ident, &ast.attrs),
55        other => panic!("can't derive `Decodable` on: {:?}", other),
56    }
57}
58
59/// Custom derive for `simple_tlv::Encodable`
60fn derive_encodable(s: Structure<'_>) -> TokenStream {
61    let ast = s.ast();
62
63    // TODO: enum support
64    match &ast.data {
65        syn::Data::Struct(data) => DeriveEncodableStruct::derive(s, data, &ast.ident, &ast.attrs),
66        other => panic!("can't derive `Encodable` on: {:?}", other),
67    }
68}
69
70/// Attributes of a field
71#[derive(Debug)]
72struct FieldAttrs {
73    /// Name of the field
74    pub name: Ident,
75
76    /// Value of the `#[tlv(tag = "...")]` attribute if provided
77    pub tag: u8,
78
79    /// Whether the `#[tlv(slice)]` attribute was set
80    pub slice: bool
81}
82
83impl FieldAttrs {
84    /// Parse the attributes of a field
85    fn new(field: &Field) -> Self {
86        let name = field
87            .ident
88            .as_ref()
89            .cloned()
90            .expect("no name on struct field i.e. tuple structs unsupported");
91
92        let (tag, slice) = extract_attrs(&name, &field.attrs);
93
94        Self { name, tag, slice }
95    }
96}
97
98fn extract_attrs_optional_tag(name: &Ident, attrs: &[Attribute]) -> (Option<u8>, bool) {
99    let mut tag = None;
100    let mut slice = false;
101
102    for attr in attrs {
103        if !attr.path.is_ident("tlv") {
104            continue;
105        }
106
107        match attr.parse_meta().expect("error parsing `tlv` attribute") {
108            Meta::List(MetaList { nested, .. }) if !nested.is_empty() => {
109                for entry in nested {
110                    match entry {
111                        NestedMeta::Meta(Meta::Path(path)) => {
112                            if !path.is_ident("slice") {
113                                panic!("unknown `tlv` attribute for field `{}`: {:?}", name, path);
114                            }
115                            slice = true;
116                        }
117                        NestedMeta::Meta(Meta::NameValue(MetaNameValue {
118                            path,
119                            lit: Lit::Str(lit_str),
120                            ..
121                        })) => {
122                            // Parse the `type = "..."` attribute
123                            if !path.is_ident("tag") {
124                                panic!("unknown `tlv` attribute for field `{}`: {:?}", name, path);
125                            }
126
127                            if tag.is_some() {
128                                panic!("duplicate SIMPLE-TLV `tag` attribute for field: {}", name);
129                            }
130
131                            let possibly_with_prefix = lit_str.value();
132                            let without_prefix = possibly_with_prefix.trim_start_matches("0x");
133                            let tag_value = u8::from_str_radix(without_prefix, 16).expect("tag values must be between one and 254");
134                            if tag_value == 0 || tag_value == 255 {
135                                panic!("SIMPLE-TLV tags must not be zero or 255");
136                            }
137                            tag = Some(tag_value);
138                        }
139                        other => panic!(
140                            "a malformed `tlv` attribute for field `{}`: {:?}",
141                            name, other
142                        ),
143                    }
144                }
145            }
146            other => panic!(
147                "malformed `tlv` attribute for field `{}`: {:#?}",
148                name, other
149            ),
150        }
151    }
152
153    (tag, slice)
154}
155
156fn extract_attrs(name: &Ident, attrs: &[Attribute]) -> (u8, bool) {
157    let (tag, slice) = extract_attrs_optional_tag(name, attrs);
158
159    if let Some(tag) = tag {
160        (tag, slice)
161    } else {
162        panic!("SIMPLE-TLV tag missing for `{}`", name);
163    }
164}