tier-derive 0.1.17

Derive macros for tier Rust configuration metadata
Documentation
use syn::Attribute;

use crate::serde_attrs::model::{SerdeFieldAttrs, SerdeFieldContext};
use crate::syntax::{consume_unused_meta, parse_string_value, unraw};

use super::rename_meta::parse_rename_meta;

pub(crate) fn parse_serde_field_attrs(
    attributes: &[Attribute],
    field_ident: &syn::Ident,
    context: SerdeFieldContext,
) -> syn::Result<SerdeFieldAttrs> {
    let base_name = unraw(field_ident);
    let mut rename_serialize = None;
    let mut rename_deserialize = None;
    let mut aliases = Vec::new();
    let mut flatten = false;
    let mut skip_metadata = false;
    let mut has_default = context.default_fields;

    for attribute in attributes {
        if !attribute.path().is_ident("serde") {
            continue;
        }

        attribute.parse_nested_meta(|meta| {
            if meta.path.is_ident("rename") {
                parse_rename_meta(meta, &mut rename_serialize, &mut rename_deserialize)?;
                return Ok(());
            }
            if meta.path.is_ident("alias") {
                aliases.push(parse_string_value(meta)?);
                return Ok(());
            }
            if meta.path.is_ident("flatten") {
                flatten = true;
                return Ok(());
            }
            if meta.path.is_ident("default") {
                has_default = true;
                consume_unused_meta(meta)?;
                return Ok(());
            }
            if meta.path.is_ident("skip") || meta.path.is_ident("skip_deserializing") {
                skip_metadata = true;
                return Ok(());
            }
            consume_unused_meta(meta)?;
            Ok(())
        })?;
    }

    let has_explicit_rename = rename_serialize.is_some() || rename_deserialize.is_some();

    let canonical_name = rename_serialize
        .or_else(|| {
            context
                .rename_serialize
                .map(|rule| rule.apply_to_field(&base_name))
        })
        .unwrap_or_else(|| base_name.clone());
    let deserialize_name = rename_deserialize
        .or_else(|| {
            context
                .rename_deserialize
                .map(|rule| rule.apply_to_field(&base_name))
        })
        .unwrap_or_else(|| base_name.clone());

    if deserialize_name != canonical_name {
        aliases.push(deserialize_name);
    }

    if flatten && (!aliases.is_empty() || has_explicit_rename) {
        return Err(syn::Error::new_spanned(
            field_ident,
            "flattened fields cannot use serde rename or alias attributes",
        ));
    }

    aliases.retain(|alias| alias != &canonical_name);
    aliases.sort();
    aliases.dedup();

    Ok(SerdeFieldAttrs {
        canonical_name,
        aliases,
        flatten,
        skip_metadata,
        has_default,
    })
}

pub(crate) fn has_field_naming_attrs(attributes: &[Attribute]) -> syn::Result<bool> {
    let mut has_naming = false;
    for attribute in attributes {
        if !attribute.path().is_ident("serde") {
            continue;
        }

        attribute.parse_nested_meta(|meta| {
            if meta.path.is_ident("rename")
                || meta.path.is_ident("alias")
                || meta.path.is_ident("flatten")
                || meta.path.is_ident("default")
            {
                has_naming = true;
            }
            consume_unused_meta(meta)?;
            Ok(())
        })?;
    }

    Ok(has_naming)
}