deepmerge-derive 0.1.0

Derive macro for the DeepMerge trait with typed attribute support
Documentation
//! `DeepMerge` derive macro implementation
//!
//! This crate provides the `#[derive(DeepMerge)]` macro for automatically implementing
//! the `DeepMerge` trait with customizable merge policies.

#![forbid(unsafe_code)]

mod parse;
mod validate;
mod codegen;

use proc_macro::TokenStream;
use syn::parse_macro_input;
use darling::FromDeriveInput;

/// Derive macro for the `DeepMerge` trait
///
/// # Basic Usage
///
/// ```rust,ignore
/// #[derive(DeepMerge)]
/// struct Config {
///     name: String,
///     value: i32,
/// }
/// ```
///
/// # Struct-level Policies
///
/// Configure default merge behavior for all fields:
///
/// ```rust,ignore
/// #[derive(DeepMerge)]
/// #[merge(policy(string = concat, sequence = append, bool = true_wins))]
/// struct Config {
///     title: String,
///     tags: Vec<String>,
///     enabled: bool,
/// }
/// ```
///
/// # Field-level Attributes
///
/// Override merge behavior for specific fields:
///
/// ```rust,ignore
/// #[derive(DeepMerge)]
/// struct Config {
///     #[merge(skip)]              // Skip merging this field
///     id: String,
///     
///     #[merge(string = keep)]     // Keep original value
///     version: String,
///     
///     #[merge(with = "custom_merge_fn")]  // Use custom merge function
///     special: SpecialType,
/// }
/// ```
///
/// # Supported Policies
///
/// - **String**: `concat`, `replace`, `keep`
/// - **Sequence**: `append`, `prepend`, `union`, `extend`, `intersect`
/// - **Boolean**: `true_wins`, `false_wins`, `replace`, `keep`
/// - **Number**: `sum`, `max`, `min`, `replace`, `keep`
/// - **Map**: `overlay`, `union`, `left`, `right`
/// - **Option**: `take`, `preserve`, `or_left`
/// - **Condition**: `always`, `non_empty`, `non_default`, `some`, `changed`
///
/// # Panics
///
/// This function panics if validation diagnostics cannot be processed correctly.
#[proc_macro_derive(DeepMerge, attributes(merge))]
pub fn derive_deep_merge(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as syn::DeriveInput);
    
    // Parse the input
    let merge_input = match parse::MergeInput::from_derive_input(&input) {
        Ok(v) => v,
        Err(e) => return TokenStream::from(e.write_errors()),
    };
    
    // Parse struct-level policy
    let struct_policy = match parse::parse_struct_policy(&merge_input.attrs) {
        Ok(p) => p,
        Err(e) => return TokenStream::from(e.write_errors()),
    };
    
    // Validate struct-level policy
    if let Some(ref policy) = struct_policy {
        let diagnostics = validate::validate_struct_policy(policy);
        if !diagnostics.is_empty() {
            // Return the first error
            return TokenStream::from(diagnostics.into_iter().next().unwrap().write_errors());
        }
    }
    
    // Generate implementation based on data type
    let output = match merge_input.data {
        darling::ast::Data::Struct(ref fields) => {
            let fields: Vec<_> = fields.fields.clone();
            
            // Validate field-level constraints
            let analyzer = validate::ConstraintAnalyzer::new();
            for field in &fields {
                let config = parse::parse_field_typed_policies(field);
                let diagnostics = validate::validate_field_config(field, &config, &analyzer);
                if !diagnostics.is_empty() {
                    // Return the first error with proper span
                    return TokenStream::from(diagnostics.into_iter().next().unwrap().write_errors());
                }
            }
            
            // Generate struct implementation
            codegen::generate_struct_impl(
                &merge_input.ident,
                &merge_input.generics,
                &fields,
                struct_policy.as_ref(),
                merge_input.generics.where_clause.as_ref(),
            )
        }
        darling::ast::Data::Enum(_) => {
            // Generate enum implementation
            codegen::generate_enum_impl(
                &merge_input.ident,
                &merge_input.generics,
                merge_input.generics.where_clause.as_ref(),
            )
        }
    };
    
    TokenStream::from(output)
}