use crate::MUTATIS_ATTRIBUTE_NAME;
use quote::quote;
use syn::{spanned::Spanned, *};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FieldBehavior {
GenericMutator,
DefaultMutate,
}
impl FieldBehavior {
pub fn for_field(field: &Field) -> Result<Option<FieldBehavior>> {
let opt_attr = fetch_attr_from_field(field)?;
let ctor = match opt_attr {
Some(attr) => parse_attribute(attr)?,
None => Some(FieldBehavior::GenericMutator),
};
Ok(ctor)
}
pub fn needs_generic(&self) -> bool {
match self {
FieldBehavior::GenericMutator => true,
FieldBehavior::DefaultMutate => false,
}
}
}
fn fetch_attr_from_field(field: &Field) -> Result<Option<&Attribute>> {
let found_attributes: Vec<_> = field
.attrs
.iter()
.filter(|a| {
let path = a.path();
let name = quote!(#path).to_string();
name == MUTATIS_ATTRIBUTE_NAME
})
.collect();
if found_attributes.len() > 1 {
let name = field.ident.as_ref().unwrap();
let msg = format!(
"Multiple conflicting #[{MUTATIS_ATTRIBUTE_NAME}] attributes found on field `{name}`"
);
return Err(syn::Error::new(field.span(), msg));
}
Ok(found_attributes.into_iter().next())
}
fn parse_attribute(attr: &Attribute) -> Result<Option<FieldBehavior>> {
if let Meta::List(ref meta_list) = attr.meta {
parse_attribute_internals(meta_list)
} else {
let msg = format!("#[{MUTATIS_ATTRIBUTE_NAME}] must contain a group");
Err(syn::Error::new(attr.span(), msg))
}
}
fn parse_attribute_internals(meta_list: &MetaList) -> Result<Option<FieldBehavior>> {
let mut tokens_iter = meta_list.tokens.clone().into_iter();
let token = tokens_iter.next().ok_or_else(|| {
let msg = format!("#[{MUTATIS_ATTRIBUTE_NAME}] cannot be empty.");
syn::Error::new(meta_list.span(), msg)
})?;
match token.to_string().as_ref() {
"ignore" => Ok(None),
"default_mutate" => Ok(Some(FieldBehavior::DefaultMutate)),
_ => {
let msg = format!("Unknown option for #[{MUTATIS_ATTRIBUTE_NAME}]: `{token}`");
Err(syn::Error::new(token.span(), msg))
}
}
}