runtime_struct_field_names_as_array/
lib.rs

1#![doc = include_str!("../README.md")]
2
3extern crate proc_macro;
4use proc_macro::TokenStream;
5
6use quote::quote;
7use syn::{
8  parse_macro_input, DeriveInput, Meta, PathArguments, Type,
9};
10
11/// Derives the `FieldNamesAsArray` procedural macro.
12///
13/// # Panics
14///
15/// If the token stream is not coming from a named struct or if
16/// the `field_names_as_array` attribute is used wrongfully, deriving
17/// this macro will fail.
18///
19#[proc_macro_derive(
20  FieldNamesAsArray,
21  attributes(field_names_as_array)
22)]
23pub fn derive_field_names_as_array(
24  input: TokenStream,
25) -> TokenStream {
26  let input = parse_macro_input!(input as DeriveInput);
27
28  let struct_name = &input.ident;
29  let fields = match &input.data {
30    syn::Data::Struct(data) => &data.fields,
31    _ => panic!("FieldNamesAsArray can only be derived for structs"),
32  };
33
34  let mut field_exprs = Vec::new();
35
36  for field in fields.iter() {
37    let field_name = field.ident.as_ref().unwrap().to_string();
38
39    if let Some(attr) = field
40      .attrs
41      .iter()
42      .find(|a| a.path().is_ident("field_names_as_array"))
43    {
44      let field_type = &field.ty;
45      let nested_struct = if let Type::Path(type_path) = field_type {
46        if type_path.path.segments.last().unwrap().ident == "Option" {
47          if let PathArguments::AngleBracketed(arguments) =
48            &type_path.path.segments.last().unwrap().arguments
49          {
50            if let Some(generic_arg) = arguments.args.first() {
51              if let syn::GenericArgument::Type(inner_type) =
52                generic_arg
53              {
54                Some(inner_type)
55              } else {
56                None
57              }
58            } else {
59              None
60            }
61          } else {
62            None
63          }
64        } else {
65          Some(field_type)
66        }
67      } else {
68        None
69      };
70
71      let flatten: bool = match &attr.meta {
72        Meta::List(meta) => meta
73          .to_owned()
74          .tokens
75          .into_iter()
76          .find(|token| token.to_string() == "flatten")
77          .is_some(),
78        _ => false,
79      };
80
81      if flatten {
82        if let Some(nested_struct) = nested_struct {
83          field_exprs.push(quote! { <#nested_struct>::field_names_as_array().iter().map(|s| format!("{}.{}", #field_name, s)).collect::<Vec<_>>() });
84        }
85      } else {
86        field_exprs.push(quote! { vec![#field_name.to_string()] });
87      }
88    } else {
89      field_exprs.push(quote! { vec![#field_name.to_string()] });
90    }
91  }
92
93  let output = quote! {
94      impl #struct_name {
95          pub fn field_names_as_array() -> Vec<String> {
96              let mut field_names = Vec::new();
97              #( field_names.extend(#field_exprs); )*
98              field_names
99          }
100      }
101  };
102
103  output.into()
104}