rustify_derive/
parse.rs

1use std::{
2    collections::{HashMap, HashSet},
3    convert::TryFrom,
4};
5
6use crate::{EndpointAttribute, Error};
7use syn::{
8    spanned::Spanned, Attribute, Field, Ident, LitStr, Meta, MetaNameValue, NestedMeta, Type,
9};
10
11/// Returns all [Meta] values contained in a [Meta::List].
12///
13/// For example:
14/// ```
15/// #[endpoint(query, data)]
16/// ```
17/// Would return individual [Meta] values for `query` and `data`. This function
18/// fails if the [Meta::List] is empty or contains any literals.
19pub(crate) fn attr_list(attr: &Meta) -> Result<Vec<Meta>, Error> {
20    let mut result = Vec::<Meta>::new();
21    if let Meta::List(list) = &attr {
22        if list.nested.is_empty() {
23            return Err(Error::new(attr.span(), "Attribute cannot be empty"));
24        }
25
26        for nested in list.nested.iter() {
27            if let NestedMeta::Meta(nested_meta) = nested {
28                result.push(nested_meta.clone())
29            } else {
30                return Err(Error::new(
31                    nested.span(),
32                    "Attribute cannot contain any literals",
33                ));
34            }
35        }
36
37        Ok(result)
38    } else {
39        Err(Error::new(attr.span(), "Cannot parse attribute as list"))
40    }
41}
42
43/// Returns all [MetaNameValue] values contained in a [Meta::List].
44///
45/// For example:
46/// ```
47/// #[endpoint(path = "my/path", method = "POST")]
48/// ```
49/// Would return individual [MetaNameValue] values for `path` and `method`. This
50/// function fails if the [Meta::List] is empty, contains literals, or cannot
51/// be parsed as name/value pairs.
52pub(crate) fn attr_kv(attr: &Meta) -> Result<Vec<MetaNameValue>, Error> {
53    let meta_list = attr_list(attr)?;
54    let mut result = Vec::<MetaNameValue>::new();
55    for meta in meta_list.iter() {
56        if let syn::Meta::NameValue(nv_meta) = meta {
57            result.push(nv_meta.clone());
58        } else {
59            return Err(Error::new(
60                attr.span(),
61                "Cannot parse attribute as a key/value list",
62            ));
63        }
64    }
65    Ok(result)
66}
67
68/// Converts a list of [MetaNameValue] values into a [HashMap].
69///
70/// For example, assuming the below has been parsed into [MetaNameValue]'s:
71/// ```
72/// #[endpoint(path = "my/path", method = "POST")]
73/// ```
74/// Would return a [HashMap] mapping individual ID's (i.e. `path` and `method`)
75/// to their [LitStr] values (i.e. "m/path" and "POST"). This function fails if
76/// the values cannot be parsed as string literals.
77pub(crate) fn to_map(values: &[MetaNameValue]) -> Result<HashMap<Ident, LitStr>, Error> {
78    let mut map = HashMap::<Ident, LitStr>::new();
79    for value in values.iter() {
80        let id = value.path.get_ident().unwrap().clone();
81        if let syn::Lit::Str(lit) = &value.lit {
82            map.insert(id, lit.clone());
83        } else {
84            return Err(Error::new(
85                value.span(),
86                "Values must be in string literal form",
87            ));
88        }
89    }
90
91    Ok(map)
92}
93
94/// Searches a list of [Attribute]'s and returns any matching [crate::ATTR_NAME].
95pub(crate) fn attributes(attrs: &[Attribute], name: &str) -> Result<Vec<Meta>, Error> {
96    let mut result = Vec::<Meta>::new();
97    for attr in attrs.iter() {
98        let meta = attr.parse_meta().map_err(Error::from)?;
99
100        if meta.path().is_ident(name) {
101            result.push(meta);
102        }
103    }
104
105    Ok(result)
106}
107
108/// Returns a mapping of endpoint attributes to a list of their fields.
109///
110/// Parses all [Attribute]'s on the given [syn::Field]'s, searching for any
111/// attributes which match [crate::ATTR_NAME] and creating a map of attributes
112/// to a list of their associated fields.
113pub(crate) fn field_attributes(
114    data: &syn::Data,
115) -> Result<HashMap<EndpointAttribute, Vec<Field>>, Error> {
116    let mut result = HashMap::<EndpointAttribute, Vec<Field>>::new();
117    if let syn::Data::Struct(data) = data {
118        for field in data.fields.iter() {
119            // Collect all `endpoint` attributes attached to this field
120            let attrs = attributes(&field.attrs, crate::ATTR_NAME)?;
121
122            // Add field as untagged is no attributes were found
123            if attrs.is_empty() {
124                match result.get_mut(&EndpointAttribute::Untagged) {
125                    Some(r) => {
126                        r.push(field.clone());
127                    }
128                    None => {
129                        result.insert(EndpointAttribute::Untagged, vec![field.clone()]);
130                    }
131                }
132            }
133
134            // Combine all meta parameters from each attribute
135            let attrs = attrs
136                .iter()
137                .map(attr_list)
138                .collect::<Result<Vec<Vec<Meta>>, Error>>()?;
139
140            // Flatten and eliminate duplicates
141            let attrs = attrs.into_iter().flatten().collect::<HashSet<Meta>>();
142
143            // Add this field to the list of fields for each attribute
144            for attr in attrs.iter() {
145                let attr_ty = EndpointAttribute::try_from(attr)?;
146                match result.get_mut(&attr_ty) {
147                    Some(r) => {
148                        r.push(field.clone());
149                    }
150                    None => {
151                        result.insert(attr_ty, vec![field.clone()]);
152                    }
153                }
154            }
155        }
156    }
157
158    Ok(result)
159}
160
161/// Creates and instantiates a struct from a list of [Field]s.
162///
163/// This function effectively creates a new struct from a list [Field]s and then
164/// instantiates it using the same field names from the parent struct. It's
165/// intended to be used to "split" a struct into smaller structs.
166///
167/// The new struct will automatically derive `Serialize` and any [Option] fields
168/// will automatically be excluded from serialization if their value is
169/// [Option::None].
170///
171/// The result is a [proc_macro2::TokenStream] that contains the new struct and
172/// and it's instantiation. The instantiated variable can be accessed by it's
173/// static name of `__temp`.
174pub(crate) fn fields_to_struct(fields: &[Field], attrs: &[Meta]) -> proc_macro2::TokenStream {
175    // Construct struct field definitions
176    let def = fields
177        .iter()
178        .map(|f| {
179            let id = f.ident.clone().unwrap();
180            let ty = &f.ty;
181
182            // Pass serde attributes onto our temporary struct
183            let mut attrs = Vec::<&Attribute>::new();
184            if !f.attrs.is_empty() {
185                for attr in &f.attrs {
186                    if attr.path.is_ident("serde") {
187                        attrs.push(attr);
188                    }
189                }
190            }
191
192            // If this field is an Option, don't serialize when it's None
193            if is_std_option(ty) {
194                quote! {
195                    #(#attrs)*
196                    #[serde(skip_serializing_if = "Option::is_none")]
197                    #id: &'a #ty,
198                }
199            } else {
200                quote! {
201                    #(#attrs)*
202                    #id: &'a #ty,
203                }
204            }
205        })
206        .collect::<Vec<proc_macro2::TokenStream>>();
207    let attrs = attrs
208        .iter()
209        .map(|m| quote! { #[#m]})
210        .collect::<Vec<proc_macro2::TokenStream>>();
211
212    // Construct struct instantiation
213    let inst = fields
214        .iter()
215        .map(|f| {
216            let id = f.ident.clone().unwrap();
217            quote! { #id: &self.#id, }
218        })
219        .collect::<Vec<proc_macro2::TokenStream>>();
220
221    quote! {
222        #[derive(Serialize)]
223        #(#attrs)*
224        struct __Temp<'a> {
225            #(#def)*
226        }
227
228        let __temp = __Temp {
229            #(#inst)*
230        };
231    }
232}
233
234/// Return `true`, if the type refers to [std::option::Option]
235pub(crate) fn is_std_option(ty: &Type) -> bool {
236    if let Type::Path(tp) = ty {
237        let path = &tp.path;
238        (path.leading_colon.is_none()
239            && path.segments.len() == 1
240            && path.segments[0].ident == "Option")
241            || (path.segments.len() == 3
242                && (path.segments[0].ident == "std" || path.segments[0].ident == "core")
243                && path.segments[1].ident == "option"
244                && path.segments[2].ident == "Option")
245    } else {
246        false
247    }
248}