elastic-mapping-macro 0.1.0

Procedural macros for elastic-mapping - generate Elasticsearch mappings from Rust types
Documentation
mod adjacently_tagged;
mod internally_tagged;
mod untagged;

use proc_macro2::TokenStream;
use syn::{Generics, Ident};

use crate::{
    DocumentArgs, DocumentVariant,
    codegen::gen_simple_mapping_impl,
    enums::{
        adjacently_tagged::gen_adjacently_tagged_enum,
        internally_tagged::gen_internally_tagged_enum,
        untagged::gen_untagged_enum,
    },
    error::GeneratorError,
    rename::RenameCasing,
};

/// Classification of enum representation styles
pub(crate) enum EnumRepresentation<'a> {
    /// All unit variants - serializes as text
    Unit,
    /// No tag - each variant becomes an object property
    Untagged {
        variants: &'a [DocumentVariant],
        rename_all: Option<RenameCasing>,
    },
    /// Has #[serde(tag = "...")] - internally tagged
    InternallyTagged {
        tag: String,
        variants: &'a [DocumentVariant],
        rename_all: Option<RenameCasing>,
    },
    /// Has #[serde(tag = "...", content = "...")] - adjacently tagged
    AdjacentlyTagged {
        tag: String,
        content: String,
        variants: &'a [DocumentVariant],
        rename_all: Option<RenameCasing>,
    },
}

/// Classifies an enum based on its variants and attributes
pub(crate) fn classify_enum<'a>(
    args: &'a DocumentArgs,
    variants: &'a [DocumentVariant],
) -> Result<EnumRepresentation<'a>, GeneratorError> {
    // Check for unit enums
    if variants.iter().all(|v| v.fields.is_unit()) {
        return Ok(EnumRepresentation::Unit);
    }

    // Validate content requires tag
    if args.content.is_some() && args.tag.is_none() {
        return Err(GeneratorError::Custom(
            "content attribute requires tag attribute".to_string(),
        ));
    }

    // Classify based on tag/content combination
    match (&args.tag, &args.content) {
        (Some(tag), Some(content)) => Ok(EnumRepresentation::AdjacentlyTagged {
            tag: tag.clone(),
            content: content.clone(),
            variants,
            rename_all: args.rename_all,
        }),
        (Some(tag), None) => Ok(EnumRepresentation::InternallyTagged {
            tag: tag.clone(),
            variants,
            rename_all: args.rename_all,
        }),
        (None, _) => Ok(EnumRepresentation::Untagged {
            variants,
            rename_all: args.rename_all,
        }),
    }
}

/// Generates the enum implementation based on its representation
pub(crate) fn gen_enum_impl(
    ident: &Ident,
    generics: &Generics,
    representation: EnumRepresentation,
) -> Result<TokenStream, GeneratorError> {
    match representation {
        EnumRepresentation::Unit => Ok(gen_simple_mapping_impl(ident, generics, "text")),
        EnumRepresentation::Untagged {
            variants,
            rename_all,
        } => {
            let (properties, iterable) = gen_untagged_enum(variants, rename_all)?;
            Ok(crate::codegen::gen_mapping_impl(
                ident, generics, "object", properties, iterable,
            ))
        }
        EnumRepresentation::InternallyTagged {
            tag,
            variants,
            rename_all,
        } => {
            let (properties, iterable) = gen_internally_tagged_enum(&tag, variants, rename_all)?;
            Ok(crate::codegen::gen_mapping_impl(
                ident, generics, "object", properties, iterable,
            ))
        }
        EnumRepresentation::AdjacentlyTagged {
            tag,
            content,
            variants,
            rename_all,
        } => {
            // Implement proper adjacently tagged support with content field
            let (properties, iterable) =
                gen_adjacently_tagged_enum(&tag, &content, variants, rename_all)?;
            Ok(crate::codegen::gen_mapping_impl(
                ident, generics, "object", properties, iterable,
            ))
        }
    }
}