use std::collections::HashSet;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::parse_quote;
use syn::spanned::Spanned;
use crate::types::CommaSeparatedTokenStreams;
pub fn collect_rules_from_named_struct(
attributes: &[syn::Attribute],
) -> Result<(HashSet<syn::Ident>, TokenStream), crate::Errors> {
let mut errors = vec![];
let mut rule_fields = HashSet::new();
let rules = attributes
.iter()
.filter(|attribute| attribute.path == parse_quote!(rule))
.filter_map(|attribute| match attribute.parse_meta() {
Ok(syn::Meta::List(list)) => match collect_rule(&list) {
Ok((field_ident, stream)) => {
rule_fields.extend(field_ident);
Some(stream)
}
Err(rule_errors) => {
errors.extend(rule_errors);
None
}
},
Ok(_) => {
errors.push(crate::Error::rule_need_function(&attribute.path));
None
}
Err(error) => {
errors.push(crate::Error::rule_validate_attribute_parse_error(
attribute, &error,
));
None
}
})
.collect::<Vec<_>>();
if errors.is_empty() {
Ok((rule_fields, TokenStream::from_iter(rules)))
} else {
Err(errors)
}
}
fn collect_rule(
syn::MetaList {
path, ref nested, ..
}: &syn::MetaList,
) -> Result<(HashSet<syn::Ident>, TokenStream), crate::Errors> {
let mut errors = vec![];
match nested.len() {
0 => Err(vec![crate::Error::rule_need_function(path)])?,
2.. => nested.iter().skip(1).for_each(|nested_meta| {
errors.push(crate::Error::rule_allow_single_function(nested_meta))
}),
_ => {}
}
let rule = match &nested[0] {
syn::NestedMeta::Meta(meta) => match meta {
syn::Meta::List(list) => extract_rule_from_meta_list(list),
syn::Meta::NameValue(name_value) => {
Err(vec![crate::Error::meta_name_value_not_support(name_value)])
}
syn::Meta::Path(path) => Err(vec![crate::Error::meta_path_not_support(path)]),
},
syn::NestedMeta::Lit(lit) => Err(vec![crate::Error::literal_not_support(lit)]),
};
match rule {
Ok(_) => {
if errors.is_empty() {
rule
} else {
Err(errors)
}
}
Err(rule_errors) => Err(errors.into_iter().chain(rule_errors.into_iter()).collect()),
}
}
fn extract_rule_from_meta_list(
syn::MetaList {
path: rule_fn_name,
ref nested,
..
}: &syn::MetaList,
) -> Result<(HashSet<syn::Ident>, TokenStream), crate::Errors> {
let mut errors = vec![];
if nested.is_empty() {
errors.push(crate::Error::rule_need_arguments(rule_fn_name));
}
let mut arg_idents = HashSet::new();
let rule_fn_args = nested
.iter()
.filter_map(|nested_meta| {
let arg = match nested_meta {
syn::NestedMeta::Meta(syn::Meta::Path(path)) => {
arg_idents.insert(syn::Ident::new(
&path.to_token_stream().to_string(),
path.span(),
));
Some(quote!(#path))
}
_ => None,
};
if arg.is_none() {
errors.push(crate::Error::rule_allow_path_arguments(
rule_fn_name,
nested_meta,
));
}
arg
})
.collect::<CommaSeparatedTokenStreams>();
if !errors.is_empty() {
return Err(errors);
}
Ok((
arg_idents,
quote!(
if let Err(__error) = #rule_fn_name(#rule_fn_args) {
__rule_vec_errors.push(__error);
};
),
))
}