use std::{
collections::{HashMap, HashSet},
convert::TryFrom,
};
use crate::{EndpointAttribute, Error};
use syn::{
spanned::Spanned, Attribute, Field, Ident, LitStr, Meta, MetaNameValue, NestedMeta, Type,
};
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"))
}
}
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)
}
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)
}
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)?;
match meta.path().is_ident(name) {
true => {
result.push(meta);
}
false => {}
}
}
Ok(result)
}
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() {
let attrs = attributes(&field.attrs, crate::ATTR_NAME)?;
if attrs.is_empty() {
match result.get_mut(&EndpointAttribute::Untagged) {
Some(r) => {
r.push(field.clone());
}
None => {
result.insert(EndpointAttribute::Untagged, vec![field.clone()]);
}
}
}
let attrs = attrs
.iter()
.map(attr_list)
.collect::<Result<Vec<Vec<Meta>>, Error>>()?;
let attrs = attrs.into_iter().flatten().collect::<HashSet<Meta>>();
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)
}
pub(crate) fn fields_to_struct(fields: &[Field], attrs: &[Meta]) -> proc_macro2::TokenStream {
let def = fields
.iter()
.map(|f| {
let id = f.ident.clone().unwrap();
let ty = &f.ty;
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 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>>();
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)*
};
}
}
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
}
}