tier-derive 0.1.17

Derive macros for tier Rust configuration metadata
Documentation
use quote::{format_ident, quote};
use syn::{DataEnum, Fields, FieldsUnnamed, LitStr};

use crate::attr::parse_tier_attrs;
use crate::field::single_unnamed_field;
use crate::serde_attrs::{
    EnumRepresentation, SerdeContainerAttrs, SerdeFieldContext, SerdeVariantAttrs,
    enum_representation, has_field_naming_attrs, non_external_variant_field_conflicts,
    parse_serde_variant_attrs,
};
use crate::ty::metadata_target_type;

use super::field::expand_named_fields_metadata;

pub(super) fn expand_enum_metadata(
    data_enum: DataEnum,
    container_attrs: &SerdeContainerAttrs,
) -> syn::Result<Vec<proc_macro2::TokenStream>> {
    let representation = enum_representation(container_attrs)?;
    let conflicts = non_external_variant_field_conflicts(&data_enum, container_attrs)?;
    let mut tokens = vec![quote! {
        metadata.push(
            ::tier::FieldMetadata::new("").merge_strategy(::tier::MergeStrategy::Replace)
        );
    }];
    if let Some(tag) = representation.tag_field() {
        let tag_lit = LitStr::new(tag, proc_macro2::Span::call_site());
        tokens.push(quote! {
            metadata.push(::tier::FieldMetadata::new(#tag_lit));
        });
    }

    for variant in data_enum.variants {
        let variant_ident = variant.ident.clone();
        let variant_attrs =
            parse_serde_variant_attrs(&variant.attrs, &variant_ident, container_attrs)?;
        if variant_attrs.skip_metadata {
            continue;
        }

        match variant.fields {
            Fields::Named(fields) => {
                let field_tokens = expand_named_fields_metadata(
                    fields,
                    SerdeFieldContext::for_enum_variant_fields(container_attrs),
                    &format_ident!("variant_metadata"),
                    Some(&conflicts),
                )?;
                push_variant_tokens(
                    &mut tokens,
                    field_tokens,
                    &variant_attrs,
                    &representation,
                    variant_ident.span(),
                );
            }
            Fields::Unnamed(fields) => {
                let field_tokens = expand_newtype_variant_metadata(
                    fields,
                    &representation,
                    variant_ident.span(),
                    &format_ident!("variant_metadata"),
                )?;
                push_variant_tokens(
                    &mut tokens,
                    field_tokens,
                    &variant_attrs,
                    &representation,
                    variant_ident.span(),
                );
            }
            Fields::Unit => {}
        }
    }

    Ok(tokens)
}

fn push_variant_tokens(
    tokens: &mut Vec<proc_macro2::TokenStream>,
    variant_tokens: Vec<proc_macro2::TokenStream>,
    variant_attrs: &SerdeVariantAttrs,
    representation: &EnumRepresentation,
    span: proc_macro2::Span,
) {
    let variant_name_lit = LitStr::new(&variant_attrs.canonical_name, span);
    let variant_alias_lits = variant_attrs
        .aliases
        .iter()
        .map(|alias| LitStr::new(alias, span))
        .collect::<Vec<_>>();

    match representation {
        EnumRepresentation::External => {
            tokens.push(quote! {
                {
                    let mut variant_metadata = ::tier::ConfigMetadata::new();
                    #(#variant_tokens)*
                    metadata.extend(::tier::metadata::prefixed_metadata(
                        #variant_name_lit,
                        ::std::vec![#(::std::string::String::from(#variant_alias_lits)),*],
                        variant_metadata,
                    ));
                }
            });
        }
        EnumRepresentation::Adjacent { content, .. } => {
            let content_lit = LitStr::new(content, span);
            tokens.push(quote! {
                {
                    let mut variant_metadata = ::tier::ConfigMetadata::new();
                    #(#variant_tokens)*
                    metadata.extend(::tier::metadata::prefixed_metadata(
                        #content_lit,
                        ::std::vec![],
                        variant_metadata,
                    ));
                }
            });
        }
        EnumRepresentation::Internal { .. } | EnumRepresentation::Untagged => {
            tokens.push(quote! {
                {
                    let mut variant_metadata = ::tier::ConfigMetadata::new();
                    #(#variant_tokens)*
                    metadata.extend(variant_metadata);
                }
            });
        }
    }
}

fn expand_newtype_variant_metadata(
    fields: FieldsUnnamed,
    representation: &EnumRepresentation,
    span: proc_macro2::Span,
    accumulator: &proc_macro2::Ident,
) -> syn::Result<Vec<proc_macro2::TokenStream>> {
    let field = single_unnamed_field(
        fields,
        "TierConfig only supports enum tuple variants with exactly one field",
    )?;

    if matches!(representation, EnumRepresentation::Internal { .. }) {
        return Err(syn::Error::new(
            span,
            "internally tagged enums with tuple variants are not supported by TierConfig metadata",
        ));
    }

    if parse_tier_attrs(&field.attrs)?.has_any() || has_field_naming_attrs(&field.attrs)? {
        return Err(syn::Error::new_spanned(
            field,
            "tuple enum variants cannot use field-level tier or serde naming attributes",
        ));
    }

    let metadata_ty = metadata_target_type(&field.ty);
    Ok(vec![quote! {
        #accumulator.extend(<#metadata_ty as ::tier::TierMetadata>::metadata());
    }])
}