tier-derive 0.1.17

Derive macros for tier Rust configuration metadata
Documentation
use quote::quote;
use syn::{FieldsNamed, LitStr};

use crate::attr::parse_tier_attrs;
use crate::field::named_field_ident;
use crate::field_codegen::direct_field_metadata_tokens;
use crate::serde_attrs::{NonExternalFieldConflicts, SerdeFieldContext, parse_serde_field_attrs};
use crate::ty::{is_secret_type, metadata_target_type};

use super::docs::doc_comment;
use super::validate::{validate_merge_strategy, validate_validation_attrs};

pub(super) fn expand_named_fields_metadata(
    fields: FieldsNamed,
    context: SerdeFieldContext,
    accumulator: &proc_macro2::Ident,
    conflicts: Option<&NonExternalFieldConflicts>,
) -> syn::Result<Vec<proc_macro2::TokenStream>> {
    let mut field_tokens = Vec::new();

    for field in fields.named {
        field_tokens.extend(expand_named_field_metadata(
            field,
            context,
            accumulator,
            conflicts,
        )?);
    }

    Ok(field_tokens)
}

fn expand_named_field_metadata(
    field: syn::Field,
    context: SerdeFieldContext,
    accumulator: &proc_macro2::Ident,
    conflicts: Option<&NonExternalFieldConflicts>,
) -> syn::Result<Vec<proc_macro2::TokenStream>> {
    let field_ident = named_field_ident(&field)?;
    let mut serde_attrs = parse_serde_field_attrs(&field.attrs, &field_ident, context)?;
    let mut attrs = parse_tier_attrs(&field.attrs)?;
    if attrs.doc.is_none() {
        attrs.doc = doc_comment(&field.attrs);
    }

    if serde_attrs.skip_metadata {
        if attrs.has_any() {
            return Err(syn::Error::new_spanned(
                field_ident,
                "skipped fields cannot use tier metadata attributes",
            ));
        }
        return Ok(Vec::new());
    }

    if serde_attrs.flatten && attrs.has_any() {
        return Err(syn::Error::new_spanned(
            field_ident,
            "flattened fields cannot use tier metadata attributes",
        ));
    }

    if let Some(conflicts) = conflicts {
        if conflicts
            .skipped_fields
            .contains(&serde_attrs.canonical_name)
        {
            return Ok(Vec::new());
        }
        serde_attrs
            .aliases
            .retain(|alias| !conflicts.skipped_aliases.contains(alias));
        if attrs
            .env
            .as_ref()
            .is_some_and(|env| conflicts.skipped_envs.contains(env))
        {
            attrs.env = None;
        }
    }

    validate_merge_strategy(&attrs, &field.ty)?;
    validate_validation_attrs(&attrs, &field_ident)?;

    let field_type = field.ty;
    let metadata_ty = metadata_target_type(&field_type);
    let canonical_name_lit = LitStr::new(&serde_attrs.canonical_name, field_ident.span());
    let alias_lits = serde_attrs
        .aliases
        .iter()
        .map(|alias| LitStr::new(alias, field_ident.span()))
        .collect::<Vec<_>>();

    if serde_attrs.flatten {
        return Ok(vec![quote! {
            #accumulator.extend(<#metadata_ty as ::tier::TierMetadata>::metadata());
        }]);
    }

    let nested_metadata = if attrs.leaf {
        quote! { ::tier::ConfigMetadata::new() }
    } else {
        quote! { <#metadata_ty as ::tier::TierMetadata>::metadata() }
    };

    Ok(vec![
        quote! {
            #accumulator.extend(::tier::metadata::prefixed_metadata(
                #canonical_name_lit,
                ::std::vec![#(::std::string::String::from(#alias_lits)),*],
                #nested_metadata,
            ));
        },
        direct_field_metadata_tokens(
            accumulator,
            &canonical_name_lit,
            &alias_lits,
            &serde_attrs,
            &attrs,
            is_secret_type(metadata_ty),
        )?,
    ])
}