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
}
}