rustify_derive/
parse.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
use std::{
    collections::{HashMap, HashSet},
    convert::TryFrom,
};

use crate::{EndpointAttribute, Error};
use syn::{
    spanned::Spanned, Attribute, Field, Ident, LitStr, Meta, MetaNameValue, NestedMeta, Type,
};

/// Returns all [Meta] values contained in a [Meta::List].
///
/// For example:
/// ```
/// #[endpoint(query, data)]
/// ```
/// Would return individual [Meta] values for `query` and `data`. This function
/// fails if the [Meta::List] is empty or contains any literals.
pub(crate) fn attr_list(attr: &Meta) -> Result<Vec<Meta>, Error> {
    let mut result = Vec::<Meta>::new();
    if let Meta::List(list) = &attr {
        if list.nested.is_empty() {
            return Err(Error::new(attr.span(), "Attribute cannot be empty"));
        }

        for nested in list.nested.iter() {
            if let NestedMeta::Meta(nested_meta) = nested {
                result.push(nested_meta.clone())
            } else {
                return Err(Error::new(
                    nested.span(),
                    "Attribute cannot contain any literals",
                ));
            }
        }

        Ok(result)
    } else {
        Err(Error::new(attr.span(), "Cannot parse attribute as list"))
    }
}

/// Returns all [MetaNameValue] values contained in a [Meta::List].
///
/// For example:
/// ```
/// #[endpoint(path = "my/path", method = "POST")]
/// ```
/// Would return individual [MetaNameValue] values for `path` and `method`. This
/// function fails if the [Meta::List] is empty, contains literals, or cannot
/// be parsed as name/value pairs.
pub(crate) fn attr_kv(attr: &Meta) -> Result<Vec<MetaNameValue>, Error> {
    let meta_list = attr_list(attr)?;
    let mut result = Vec::<MetaNameValue>::new();
    for meta in meta_list.iter() {
        if let syn::Meta::NameValue(nv_meta) = meta {
            result.push(nv_meta.clone());
        } else {
            return Err(Error::new(
                attr.span(),
                "Cannot parse attribute as a key/value list",
            ));
        }
    }
    Ok(result)
}

/// Converts a list of [MetaNameValue] values into a [HashMap].
///
/// For example, assuming the below has been parsed into [MetaNameValue]'s:
/// ```
/// #[endpoint(path = "my/path", method = "POST")]
/// ```
/// Would return a [HashMap] mapping individual ID's (i.e. `path` and `method`)
/// to their [LitStr] values (i.e. "m/path" and "POST"). This function fails if
/// the values cannot be parsed as string literals.
pub(crate) fn to_map(values: &[MetaNameValue]) -> Result<HashMap<Ident, LitStr>, Error> {
    let mut map = HashMap::<Ident, LitStr>::new();
    for value in values.iter() {
        let id = value.path.get_ident().unwrap().clone();
        if let syn::Lit::Str(lit) = &value.lit {
            map.insert(id, lit.clone());
        } else {
            return Err(Error::new(
                value.span(),
                "Values must be in string literal form",
            ));
        }
    }

    Ok(map)
}

/// Searches a list of [Attribute]'s and returns any matching [crate::ATTR_NAME].
pub(crate) fn attributes(attrs: &[Attribute], name: &str) -> Result<Vec<Meta>, Error> {
    let mut result = Vec::<Meta>::new();
    for attr in attrs.iter() {
        let meta = attr.parse_meta().map_err(Error::from)?;

        if meta.path().is_ident(name) {
            result.push(meta);
        }
    }

    Ok(result)
}

/// Returns a mapping of endpoint attributes to a list of their fields.
///
/// Parses all [Attribute]'s on the given [syn::Field]'s, searching for any
/// attributes which match [crate::ATTR_NAME] and creating a map of attributes
/// to a list of their associated fields.
pub(crate) fn field_attributes(
    data: &syn::Data,
) -> Result<HashMap<EndpointAttribute, Vec<Field>>, Error> {
    let mut result = HashMap::<EndpointAttribute, Vec<Field>>::new();
    if let syn::Data::Struct(data) = data {
        for field in data.fields.iter() {
            // Collect all `endpoint` attributes attached to this field
            let attrs = attributes(&field.attrs, crate::ATTR_NAME)?;

            // Add field as untagged is no attributes were found
            if attrs.is_empty() {
                match result.get_mut(&EndpointAttribute::Untagged) {
                    Some(r) => {
                        r.push(field.clone());
                    }
                    None => {
                        result.insert(EndpointAttribute::Untagged, vec![field.clone()]);
                    }
                }
            }

            // Combine all meta parameters from each attribute
            let attrs = attrs
                .iter()
                .map(attr_list)
                .collect::<Result<Vec<Vec<Meta>>, Error>>()?;

            // Flatten and eliminate duplicates
            let attrs = attrs.into_iter().flatten().collect::<HashSet<Meta>>();

            // Add this field to the list of fields for each attribute
            for attr in attrs.iter() {
                let attr_ty = EndpointAttribute::try_from(attr)?;
                match result.get_mut(&attr_ty) {
                    Some(r) => {
                        r.push(field.clone());
                    }
                    None => {
                        result.insert(attr_ty, vec![field.clone()]);
                    }
                }
            }
        }
    }

    Ok(result)
}

/// Creates and instantiates a struct from a list of [Field]s.
///
/// This function effectively creates a new struct from a list [Field]s and then
/// instantiates it using the same field names from the parent struct. It's
/// intended to be used to "split" a struct into smaller structs.
///
/// The new struct will automatically derive `Serialize` and any [Option] fields
/// will automatically be excluded from serialization if their value is
/// [Option::None].
///
/// The result is a [proc_macro2::TokenStream] that contains the new struct and
/// and it's instantiation. The instantiated variable can be accessed by it's
/// static name of `__temp`.
pub(crate) fn fields_to_struct(fields: &[Field], attrs: &[Meta]) -> proc_macro2::TokenStream {
    // Construct struct field definitions
    let def = fields
        .iter()
        .map(|f| {
            let id = f.ident.clone().unwrap();
            let ty = &f.ty;

            // Pass serde attributes onto our temporary struct
            let mut attrs = Vec::<&Attribute>::new();
            if !f.attrs.is_empty() {
                for attr in &f.attrs {
                    if attr.path.is_ident("serde") {
                        attrs.push(attr);
                    }
                }
            }

            // If this field is an Option, don't serialize when it's None
            if is_std_option(ty) {
                quote! {
                    #(#attrs)*
                    #[serde(skip_serializing_if = "Option::is_none")]
                    #id: &'a #ty,
                }
            } else {
                quote! {
                    #(#attrs)*
                    #id: &'a #ty,
                }
            }
        })
        .collect::<Vec<proc_macro2::TokenStream>>();
    let attrs = attrs
        .iter()
        .map(|m| quote! { #[#m]})
        .collect::<Vec<proc_macro2::TokenStream>>();

    // Construct struct instantiation
    let inst = fields
        .iter()
        .map(|f| {
            let id = f.ident.clone().unwrap();
            quote! { #id: &self.#id, }
        })
        .collect::<Vec<proc_macro2::TokenStream>>();

    quote! {
        #[derive(Serialize)]
        #(#attrs)*
        struct __Temp<'a> {
            #(#def)*
        }

        let __temp = __Temp {
            #(#inst)*
        };
    }
}

/// Return `true`, if the type refers to [std::option::Option]
pub(crate) fn is_std_option(ty: &Type) -> bool {
    if let Type::Path(tp) = ty {
        let path = &tp.path;
        (path.leading_colon.is_none()
            && path.segments.len() == 1
            && path.segments[0].ident == "Option")
            || (path.segments.len() == 3
                && (path.segments[0].ident == "std" || path.segments[0].ident == "core")
                && path.segments[1].ident == "option"
                && path.segments[2].ident == "Option")
    } else {
        false
    }
}