use crate::error::Result;
use darling::{ast::Fields, FromVariant};
use inflector::cases::snakecase::to_snake_case;
use quote::format_ident;
use syn::{Attribute, Ident, ItemEnum, Type, Visibility};
#[derive(FromVariant, Debug)]
#[darling(attributes(variantly))]
pub struct VariantInput {
pub ident: Ident,
#[darling(default)]
pub rename: Option<Ident>,
pub fields: Fields<FieldParsed>,
}
#[derive(FromField, Debug)]
#[darling(forward_attrs)]
pub struct FieldParsed {
pub ident: Option<Ident>,
pub ty: Type,
pub attrs: Vec<Attribute>,
pub vis: Visibility,
}
#[derive(Debug)]
pub struct VariantParsed {
pub ident: Ident,
pub used_name: Ident,
pub fields: Fields<FieldParsed>,
}
impl From<VariantInput> for VariantParsed {
fn from(variant: VariantInput) -> Self {
let ident = &variant.ident;
VariantParsed {
used_name: format_ident!(
"{}",
to_snake_case(&variant.rename.unwrap_or_else(|| ident.clone()).to_string())
),
ident: variant.ident,
fields: variant.fields,
}
}
}
pub fn try_parse_variants(item_enum: &ItemEnum) -> Result<Vec<VariantParsed>> {
item_enum
.variants
.iter()
.map(|variant| {
VariantInput::from_variant(variant)
.map(VariantInput::into)
.map_err(darling::Error::into)
})
.collect()
}
pub fn validate_compare<F: Fn(&VariantParsed, &VariantParsed) -> Result<()>>(
variants: &Vec<VariantParsed>,
validations: Vec<F>,
) -> Result<()> {
variants
.as_slice()
.iter()
.enumerate()
.try_for_each(|(index, variant_a)| -> Result<()> {
variants[(index + 1)..variants.len()]
.iter()
.try_for_each(|variant_b| {
validations
.iter()
.try_for_each(|validation| validation(variant_a, variant_b))
})
})
}
pub fn compare_used_names(a: &VariantParsed, b: &VariantParsed) -> Result<()> {
if a.used_name == b.used_name {
let message = format!("`{}` cannot be coerced into a unique & idiomatic snake_case function name as it would collide with the `{}` variant of the same Enum. \
use the following attribute on this or the conflicting variant to resolve: `#[variantly(rename = \"some_other_name\")]`",
&a.ident, &b.ident);
Err(syn::Error::new(a.ident.span(), message).into())
} else {
Ok(())
}
}