struct_metadata_derive/
lib.rs

1//! Derive macro for the struct-metadata package.
2
3#![warn(missing_docs, non_ascii_idents, trivial_numeric_casts,
4    noop_method_call, single_use_lifetimes, trivial_casts,
5    unused_lifetimes, nonstandard_style, variant_size_differences)]
6#![deny(keyword_idents)]
7#![warn(clippy::missing_docs_in_private_items)]
8#![allow(clippy::needless_return, clippy::while_let_on_iterator)]
9
10
11use convert_case::Casing;
12use proc_macro::{self, TokenStream};
13use quote::{quote, quote_spanned, ToTokens};
14use syn::spanned::Spanned;
15use syn::{parse_macro_input, DeriveInput, Token, Ident, LitBool};
16
17/// Derive macro for the MetadataKind trait
18#[proc_macro_derive(MetadataKind)]
19pub fn derive_metadata_kind(input: TokenStream) -> TokenStream {
20    let DeriveInput {ident, ..} = parse_macro_input!(input);
21
22    let output = quote! {
23        impl struct_metadata::MetadataKind for #ident {}
24    };
25
26    output.into()
27}
28
29/// Derive macro for the Described trait
30#[proc_macro_derive(Described, attributes(metadata, metadata_type, metadata_sequence, serde))]
31pub fn derive(input: TokenStream) -> TokenStream {
32    let DeriveInput {ident, attrs, data, ..} = parse_macro_input!(input);
33
34    let metadata_type = parse_metadata_type(&attrs);
35    let serde_attrs = _parse_serde_attrs(&attrs);
36
37    // ident will refer to the TYPE NAME, outer_name will refer to the presented name in metadata for the type
38    let outer_name = match serde_attrs.rename {
39        Some(new_name) => quote!(#new_name),
40        None => quote_spanned!(ident.span() => stringify!(#ident)),
41    };
42
43    match data {
44        syn::Data::Struct(data) => {
45
46            let kind = match data.fields {
47                syn::Fields::Named(fields) => {
48                    let mut children = vec![];
49                    let mut flattened_children = vec![];
50                    let mut flattened_metadata = vec![];
51
52                    for field in &fields.named {
53                        let SerdeFieldAttrs {rename, flatten, mut has_default, mut aliases } = _parse_serde_field_attrs(&field.attrs);
54                        has_default |= serde_attrs.has_default;
55                        let name = field.ident.clone().unwrap();
56                        let ty = &field.ty;
57                        let ty = quote_spanned!(ty.span() => <#ty as struct_metadata::Described::<#metadata_type>>::metadata());
58                        let docs = parse_doc_comment(&field.attrs);
59                        let metadata: proc_macro2::TokenStream = parse_metadata_params(&metadata_type, &field.attrs);
60
61                        let name = if let Some(rename) = rename {
62                            aliases.insert(0, rename.clone());
63                            quote!(#rename)
64                        } else if let Some(case) = serde_attrs.rename_all {
65                            let new_name = name.to_string().to_case(case);
66                            aliases.insert(0, new_name.clone());
67                            quote!(#new_name)
68                        } else {
69                            aliases.insert(0, name.to_string());
70                            quote!(stringify!(#name))
71                        };
72
73                        if flatten {
74                            flattened_children.push(ty);
75                            flattened_metadata.push(metadata);
76                        } else {
77                            children.push(quote!{struct_metadata::Entry::<#metadata_type> {
78                                label: #name,
79                                docs: #docs,
80                                metadata: #metadata,
81                                type_info: #ty,
82                                has_default: #has_default,
83                                aliases: &[#(#aliases),*]
84                            }});
85                        }
86                    }
87
88                    if flattened_children.is_empty() {
89                        quote!(struct_metadata::Kind::Struct::<#metadata_type> {
90                            name: #outer_name,
91                            children: vec![#(#children),*]
92                        })
93                    } else {
94                        quote!(struct_metadata::Kind::<#metadata_type>::new_struct(#outer_name, vec![#(#children),*], &mut [#(#flattened_children),*], &mut [#(#flattened_metadata),*]))
95                    }
96                },
97                syn::Fields::Unnamed(fields) => {
98                    if fields.unnamed.is_empty() {
99                        quote!(struct_metadata::Kind::<#metadata_type>::Struct { name: #outer_name, children: vec![] })
100                    } else if fields.unnamed.len() == 1 {
101                        let ty = &fields.unnamed[0].ty;
102                        let ty = quote_spanned!(ty.span() => <#ty as struct_metadata::Described::<#metadata_type>>::metadata());
103                        quote!(struct_metadata::Kind::<#metadata_type>::Aliased { name: #outer_name, kind: Box::new(#ty)})
104                    } else {
105                        panic!("tuple struct not supported")
106                    }
107                },
108                syn::Fields::Unit => {
109                    quote!(struct_metadata::Kind::<#metadata_type>::Struct { name: #outer_name, children: vec![] })
110                },
111            };
112
113            let docs = parse_doc_comment(&attrs);
114            let metadata: proc_macro2::TokenStream = parse_metadata_params(&metadata_type, &attrs);
115            let output = quote! {
116                impl struct_metadata::Described::<#metadata_type> for #ident {
117                    #[allow(clippy::needless_update)]
118                    fn metadata() -> struct_metadata::Descriptor::<#metadata_type> {
119                        let mut data = struct_metadata::Descriptor::<#metadata_type> {
120                            docs: #docs,
121                            kind: #kind,
122                            metadata: #metadata,
123                        };
124                        data.propagate(None);
125                        data
126                    }
127                }
128            };
129
130            output.into()
131        }
132
133        syn::Data::Enum(data) => {
134
135            let mut all_variants = vec![];
136
137            for variant in data.variants {
138
139                if !variant.fields.is_empty() {
140                    return syn::Error::new(variant.fields.span(), "Only enums without field values are supported.").into_compile_error().into()
141                }
142
143                let name = variant.ident.clone();
144                let docs = parse_doc_comment(&variant.attrs);
145                let metadata: proc_macro2::TokenStream = parse_metadata_params(&metadata_type, &variant.attrs);
146                let SerdeFieldAttrs {rename, flatten: _, has_default: _, mut aliases } = _parse_serde_field_attrs(&variant.attrs);
147
148                let name = if let Some(name) = rename {
149                    aliases.insert(0, name.clone());
150                    quote!(#name)
151                } else if let Some(case) = serde_attrs.rename_all {
152                    let new_name = name.to_string().to_case(case);
153                    aliases.insert(0, new_name.clone());
154                    quote!(#new_name)
155                } else {
156                    aliases.insert(0, name.to_string());
157                    quote_spanned!(variant.span() => stringify!(#name))
158                };
159
160                all_variants.push(quote!{struct_metadata::Variant::<#metadata_type> {
161                    label: #name,
162                    docs: #docs,
163                    metadata: #metadata,
164                    aliases: &[#(#aliases),*]
165                }});
166            }
167
168            let docs = parse_doc_comment(&attrs);
169            let metadata: proc_macro2::TokenStream = parse_metadata_params(&metadata_type, &attrs);
170            let output = quote! {
171                impl struct_metadata::Described::<#metadata_type> for #ident {
172                    fn metadata() -> struct_metadata::Descriptor::<#metadata_type> {
173                        struct_metadata::Descriptor::<#metadata_type> {
174                            docs: #docs,
175                            kind: struct_metadata::Kind::<#metadata_type>::Enum {
176                                name: #outer_name,
177                                variants: vec![#(#all_variants),*]
178                            },
179                            metadata: #metadata,
180                        }
181                    }
182                }
183            };
184
185            output.into()
186        }
187
188        _ => {
189            panic!("derive is not supported for this type")
190        }
191    }
192}
193
194/// Derive macro for the Described trait for enums where the varient labels provided should come
195/// from the to_string method rather than raw varient names
196#[proc_macro_derive(DescribedEnumString, attributes(metadata, metadata_type, metadata_sequence, serde, strum))]
197pub fn derive_enum_string(input: TokenStream) -> TokenStream {
198    let DeriveInput {ident, attrs, data, ..} = parse_macro_input!(input);
199
200    let metadata_type = parse_metadata_type(&attrs);
201    let strum_attr = _parse_strum_attrs(&attrs);
202
203    match data {
204        syn::Data::Enum(data) => {
205
206            let mut all_variants = vec![];
207
208            for variant in data.variants {
209
210                if !variant.fields.is_empty() {
211                    return syn::Error::new(variant.fields.span(), "Only enums without field values are supported.").into_compile_error().into()
212                }
213
214                let name = variant.ident.clone();
215                let docs = parse_doc_comment(&variant.attrs);
216                let metadata: proc_macro2::TokenStream = parse_metadata_params(&metadata_type, &variant.attrs);
217
218                // let name: proc_macro2::TokenStream = quote_spanned!(variant.span() => stringify!(#name));
219                let name = match strum_attr.serialize_all {
220                    Some(case) => {
221                        let new_name = name.to_string().to_case(case);
222                        quote!(#new_name)
223                    },
224                    None => {
225                        quote!(#name)
226                    },
227                };
228
229                all_variants.push(quote!{struct_metadata::Variant::<#metadata_type> {
230                    label: #name,
231                    docs: #docs,
232                    metadata: #metadata,
233                    aliases: &[#name]
234                }});
235            }
236
237            let docs = parse_doc_comment(&attrs);
238            let metadata: proc_macro2::TokenStream = parse_metadata_params(&metadata_type, &attrs);
239            let output = quote! {
240                impl struct_metadata::Described::<#metadata_type> for #ident {
241                    fn metadata() -> struct_metadata::Descriptor::<#metadata_type> {
242                        struct_metadata::Descriptor::<#metadata_type> {
243                            docs: #docs,
244                            kind: struct_metadata::Kind::<#metadata_type>::Enum {
245                                name: stringify!(#ident),
246                                variants: vec![#(#all_variants),*]
247                            },
248                            metadata: #metadata,
249                        }
250                    }
251                }
252            };
253
254            output.into()
255        }
256
257        _ => {
258            panic!("DescribedEnumString only applies to enum types")
259        }
260    }
261}
262
263
264/// Helper function to pull out docstrings
265/// syn always stores comments as attribute pairs with the path "doc"
266fn parse_doc_comment(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
267    let mut lines = vec![];
268    for attr in attrs {
269        if let syn::AttrStyle::Inner(_) = attr.style {
270            continue
271        }
272
273        if let syn::Meta::NameValue(pair) = &attr.meta {
274            if !pair.path.is_ident("doc") { continue }
275            if let Some(doc) = pair.value.span().source_text() {
276                let doc = doc.strip_prefix("///").unwrap_or(&doc);
277                lines.push(doc.trim().to_string());
278            }
279        }
280    }
281
282    if lines.is_empty() {
283        quote! { None }
284    } else {
285        quote!{ Some(vec![
286            #( #lines, )*
287        ])}
288    }
289}
290
291/// Description of metadata type being used
292enum MetadataKind {
293    /// Metadata is being described by a struct
294    Type(proc_macro2::TokenStream, bool),
295    /// Metadata is being described by something that implements FromIterator<(&'static str, &'static str)>
296    Sequence(proc_macro2::TokenStream),
297}
298
299impl ToTokens for MetadataKind {
300    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
301        match self {
302            MetadataKind::Type(has, _) => tokens.extend(has.clone()),
303            MetadataKind::Sequence(has) => tokens.extend(has.clone()),
304        }
305    }
306}
307
308/// Helper function to find the type being used for metadata
309fn parse_metadata_type(attrs: &[syn::Attribute]) -> MetadataKind {
310    let metadata_type = _parse_metadata_type(attrs);
311    let metadata_sequence = _parse_metadata_sequence(attrs);
312    match metadata_type {
313        Some((tokens, defaults)) => match metadata_sequence {
314            Some(_) => panic!("Only one of metadata_type and metadata_sequence may be set."),
315            None => MetadataKind::Type(tokens, defaults),
316        },
317        None => match metadata_sequence {
318            Some(tokens) => MetadataKind::Sequence(tokens),
319            None => MetadataKind::Sequence(quote!(std::collections::HashMap<&'static str, &'static str>)),
320        },
321    }
322}
323
324/// Parse metadata type if its a sequence type in the form of
325/// #[metadata_sequence(Vec<(&'static str, &'static str)>)]
326/// syn stores them as a metadata path followed by a list of tokens
327fn _parse_metadata_sequence(attrs: &[syn::Attribute]) -> Option<proc_macro2::TokenStream> {
328    for attr in attrs {
329        if let syn::Meta::List(meta) = &attr.meta {
330            if meta.path.is_ident("metadata_sequence") {
331                let MetadataType(name, _) = syn::parse2(meta.tokens.clone()).expect("Invalid metadata_sequence");
332                return Some(quote!{ #name })    
333            }
334        }
335    }
336    None
337}
338
339/// Parse metadata type if its a struct in the form of
340/// #[metadata_type(Properties, defaults: false)]
341fn _parse_metadata_type(attrs: &[syn::Attribute]) -> Option<(proc_macro2::TokenStream, bool)> {
342    for attr in attrs {
343        if let syn::Meta::List(meta) = &attr.meta {
344            if meta.path.is_ident("metadata_type") {
345                let MetadataType(name, defaults) = syn::parse2(meta.tokens.clone()).expect("Invalid metadata_type");
346                return Some((quote!{ #name }, defaults))    
347            }
348        }
349    }
350    None
351}
352
353/// Parse out the metadata attribute
354fn parse_metadata_params(metatype: &MetadataKind, attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
355    match metatype {
356        MetadataKind::Sequence(_) => {
357            for attr in attrs {
358                if let syn::Meta::List(meta) = &attr.meta {
359                    if meta.path.is_ident("metadata") {
360                        let MetadataParams (names, values) = syn::parse2(meta.tokens.clone())
361                            .unwrap_or_else(|_| panic!("Invalid metadata attribute: {}", meta.tokens));
362                        return quote!{ [
363                            #((stringify!(#names), stringify!(#values).into())),*
364                        ].into_iter().collect() }
365                    }
366                }
367            }
368            quote!{ Default::default() }
369        },
370        MetadataKind::Type(type_name, defaults) => {
371            let defaults = if *defaults {
372                quote!(..Default::default())
373            } else {
374                quote!()
375            };
376
377            for attr in attrs {
378                if let syn::Meta::List(meta) = &attr.meta {
379                    if meta.path.is_ident("metadata") {
380                        let MetadataParams (names, values) = syn::parse2(meta.tokens.clone())
381                            .unwrap_or_else(|_| panic!("Invalid metadata attribute: {}", meta.tokens));
382                        return quote!{
383                            #type_name {
384                                #(#names: #values.into(),)*
385                                #defaults
386                            }
387                        }
388                    }
389                }
390            }
391            quote!{ Default::default() }
392        }
393    }
394}
395
396/// Helper to parse out the metadata_type attribute
397struct MetadataType(syn::Type, bool);
398impl syn::parse::Parse for MetadataType {
399    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
400        // let content;
401        // syn::parenthesized!(content in input);
402        let key = input.parse()?;
403
404        if input.is_empty() {
405            return Ok(MetadataType(key, true));
406        }
407
408        let defaults;
409
410        input.parse::<Token![,]>()?;
411
412        let param: Ident = input.parse()?;
413        if input.peek(Token![:]) {
414            input.parse::<Token![:]>()?;
415        } else {
416            input.parse::<Token![=]>()?;
417        }
418        let value: LitBool = input.parse()?;
419
420        if param == "defaults" {
421            defaults = value.value;
422        } else {
423            panic!("Unknown type parameter: {param}")
424        }
425
426        Ok(MetadataType(key, defaults))
427    }
428}
429
430/// Helper to parse out the metadata attribute
431struct MetadataParams(Vec<syn::Ident>, Vec<syn::Expr>);
432impl syn::parse::Parse for MetadataParams {
433    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
434        // let content;
435        // syn::parenthesized!(content in input);
436        // let lifetime = content.parse()?;
437        let mut names = vec![];
438        let mut values = vec![];
439
440        loop {
441            if input.is_empty() {
442                break
443            }
444
445            let key = input.parse()?;
446            if input.peek(Token![:]) {
447                input.parse::<Token![:]>()?;
448            } else {
449                input.parse::<Token![=]>()?;
450            }
451            let value = input.parse()?;
452            names.push(key);
453            values.push(value);
454
455            if input.is_empty() {
456                break
457            }
458            input.parse::<Token![,]>()?;
459        }
460
461        Ok(MetadataParams(names, values))
462    }
463}
464
465/// Parse metadata type if its a struct
466fn _parse_serde_field_attrs(attrs: &[syn::Attribute]) -> SerdeFieldAttrs {
467    for attr in attrs {
468        if let syn::Meta::List(meta) = &attr.meta {
469            if meta.path.is_ident("serde") {
470                return syn::parse2(meta.tokens.clone()).expect("Invalid serde");
471            }
472        }
473    }
474    Default::default()
475}
476
477/// Helper to parse out the serde attribute
478#[derive(Default)]
479struct SerdeFieldAttrs {
480    /// Contains new name if this field is renamed
481    rename: Option<String>,
482    /// should the contents of this attribute be flattened into the parent?
483    /// Only does something if the child is a struct
484    flatten: bool,
485    /// has a default been defined on this field
486    has_default: bool,
487    /// other names a field might be labled under
488    aliases: Vec<String>
489}
490
491impl syn::parse::Parse for SerdeFieldAttrs {
492    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
493        // let content;
494        // syn::parenthesized!(content in input);
495
496        let mut out = SerdeFieldAttrs::default();
497        // let mut names: Vec<syn::Ident> = vec![];
498        // let mut values: Vec<syn::Expr> = vec![];
499
500        loop {
501            let key: syn::Ident = input.parse()?;
502            if input.peek(Token![=]) {
503                input.parse::<Token![=]>()?;
504
505                let value: syn::LitStr = input.parse()?;
506
507                if key == "rename" {
508                    out.rename = Some(value.value());
509                }
510
511                if key == "alias" {
512                    out.aliases.push(value.value());
513                }
514            }
515
516            if key == "default" {
517                out.has_default = true;
518            }
519
520            if key == "flatten" {
521                out.flatten = true;
522            }
523
524            if input.is_empty() {
525                break
526            }
527            input.parse::<Token![,]>()?;
528        }
529
530//         Ok(MetadataParams(names, values))
531        Ok(out)
532    }
533}
534
535
536/// Parse metadata type if its a struct
537fn _parse_serde_attrs(attrs: &[syn::Attribute]) -> SerdeAttrs {
538    for attr in attrs {
539        if let syn::Meta::List(meta) = &attr.meta {
540            if meta.path.is_ident("serde") {
541                return syn::parse2(meta.tokens.clone()).expect("Invalid serde");
542            }
543        }
544    }
545    Default::default()
546}
547
548/// Helper to parse out the serde attribute
549#[derive(Default)]
550struct SerdeAttrs {
551    /// Contains new name if this field is renamed
552    rename: Option<String>,
553    /// Rename all of the varients or fields of this container according to the given scheme
554    rename_all: Option<convert_case::Case>,
555    /// should all the fields have a default inserted from the struct default
556    has_default: bool,
557}
558
559impl syn::parse::Parse for SerdeAttrs {
560    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
561        // let content;
562        // syn::parenthesized!(content in input);
563
564        let mut out = SerdeAttrs::default();
565        // let mut names: Vec<syn::Ident> = vec![];
566        // let mut values: Vec<syn::Expr> = vec![];
567
568        loop {
569            let key: syn::Ident = input.parse()?;
570            if input.peek(Token![=]) {
571                input.parse::<Token![=]>()?;
572
573                let value: syn::LitStr = input.parse()?;
574
575                if key == "rename" {
576                    out.rename = Some(value.value());
577                }
578
579                if key == "rename_all" {
580                    out.rename_all = Some(fetch_case(&value)?);
581                }
582            }
583
584            if key == "default" {
585                out.has_default = true;
586            }
587
588            if input.is_empty() {
589                break
590            }
591            input.parse::<Token![,]>()?;
592        }
593
594        Ok(out)
595    }
596}
597
598
599/// Parse metadata type if its a struct
600fn _parse_strum_attrs(attrs: &[syn::Attribute]) -> StrumAttrs {
601    for attr in attrs {
602        if let syn::Meta::List(meta) = &attr.meta {
603            if meta.path.is_ident("strum") {
604                return syn::parse2(meta.tokens.clone()).expect("Invalid strum");
605            }
606        }
607    }
608    Default::default()
609}
610
611/// Helper to parse out the serde attribute
612#[derive(Default)]
613struct StrumAttrs {
614    /// Rename all of the varients or fields of this container according to the given scheme
615    serialize_all: Option<convert_case::Case>,
616}
617
618impl syn::parse::Parse for StrumAttrs {
619    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
620        // let content;
621        // syn::parenthesized!(content in input);
622
623        let mut out = StrumAttrs::default();
624        // let mut names: Vec<syn::Ident> = vec![];
625        // let mut values: Vec<syn::Expr> = vec![];
626
627        loop {
628            let key: syn::Ident = input.parse()?;
629            if input.peek(Token![=]) {
630                input.parse::<Token![=]>()?;
631
632                let value: syn::LitStr = input.parse()?;
633
634                if key == "serialize_all" {
635                    out.serialize_all = Some(fetch_case(&value)?);
636                }
637            }
638
639            if input.is_empty() {
640                break
641            }
642            input.parse::<Token![,]>()?;
643        }
644
645        Ok(out)
646    }
647}
648
649
650/// Determine the case conversion scheme for a given name
651fn fetch_case(name: &syn::LitStr) -> syn::Result<convert_case::Case> {
652    Ok(match name.value().to_lowercase().as_str() {
653        "lowercase" | "lower" => convert_case::Case::Lower,
654        "uppercase" | "upper" => convert_case::Case::Upper,
655        "pascalcase" | "pascal" | "uppercamel" => convert_case::Case::Pascal,
656        "camelcase" | "camel" => convert_case::Case::Camel,
657        "snake_case" => convert_case::Case::Snake,
658        "upper_snake_case" | "screaming_snake_case" => convert_case::Case::UpperSnake,
659        "kebab_case" => convert_case::Case::Kebab,
660        "upper_kebab_case" | "screaming_kebab_case" => convert_case::Case::UpperKebab,
661        _ => return Err(syn::Error::new(name.span(), format!("Unsupported case string: {}", name.value())))
662    })
663}