Skip to main content

deepmerge_derive/
lib.rs

1//! `DeepMerge` derive macro implementation
2//!
3//! This crate provides the `#[derive(DeepMerge)]` macro for automatically implementing
4//! the `DeepMerge` trait with customizable merge policies.
5
6#![forbid(unsafe_code)]
7
8mod parse;
9mod validate;
10mod codegen;
11
12use proc_macro::TokenStream;
13use syn::parse_macro_input;
14use darling::FromDeriveInput;
15
16/// Derive macro for the `DeepMerge` trait
17///
18/// # Basic Usage
19///
20/// ```rust,ignore
21/// #[derive(DeepMerge)]
22/// struct Config {
23///     name: String,
24///     value: i32,
25/// }
26/// ```
27///
28/// # Struct-level Policies
29///
30/// Configure default merge behavior for all fields:
31///
32/// ```rust,ignore
33/// #[derive(DeepMerge)]
34/// #[merge(policy(string = concat, sequence = append, bool = true_wins))]
35/// struct Config {
36///     title: String,
37///     tags: Vec<String>,
38///     enabled: bool,
39/// }
40/// ```
41///
42/// # Field-level Attributes
43///
44/// Override merge behavior for specific fields:
45///
46/// ```rust,ignore
47/// #[derive(DeepMerge)]
48/// struct Config {
49///     #[merge(skip)]              // Skip merging this field
50///     id: String,
51///     
52///     #[merge(string = keep)]     // Keep original value
53///     version: String,
54///     
55///     #[merge(with = "custom_merge_fn")]  // Use custom merge function
56///     special: SpecialType,
57/// }
58/// ```
59///
60/// # Supported Policies
61///
62/// - **String**: `concat`, `replace`, `keep`
63/// - **Sequence**: `append`, `prepend`, `union`, `extend`, `intersect`
64/// - **Boolean**: `true_wins`, `false_wins`, `replace`, `keep`
65/// - **Number**: `sum`, `max`, `min`, `replace`, `keep`
66/// - **Map**: `overlay`, `union`, `left`, `right`
67/// - **Option**: `take`, `preserve`, `or_left`
68/// - **Condition**: `always`, `non_empty`, `non_default`, `some`, `changed`
69///
70/// # Panics
71///
72/// This function panics if validation diagnostics cannot be processed correctly.
73#[proc_macro_derive(DeepMerge, attributes(merge))]
74pub fn derive_deep_merge(input: TokenStream) -> TokenStream {
75    let input = parse_macro_input!(input as syn::DeriveInput);
76    
77    // Parse the input
78    let merge_input = match parse::MergeInput::from_derive_input(&input) {
79        Ok(v) => v,
80        Err(e) => return TokenStream::from(e.write_errors()),
81    };
82    
83    // Parse struct-level policy
84    let struct_policy = match parse::parse_struct_policy(&merge_input.attrs) {
85        Ok(p) => p,
86        Err(e) => return TokenStream::from(e.write_errors()),
87    };
88    
89    // Validate struct-level policy
90    if let Some(ref policy) = struct_policy {
91        let diagnostics = validate::validate_struct_policy(policy);
92        if !diagnostics.is_empty() {
93            // Return the first error
94            return TokenStream::from(diagnostics.into_iter().next().unwrap().write_errors());
95        }
96    }
97    
98    // Generate implementation based on data type
99    let output = match merge_input.data {
100        darling::ast::Data::Struct(ref fields) => {
101            let fields: Vec<_> = fields.fields.clone();
102            
103            // Validate field-level constraints
104            let analyzer = validate::ConstraintAnalyzer::new();
105            for field in &fields {
106                let config = parse::parse_field_typed_policies(field);
107                let diagnostics = validate::validate_field_config(field, &config, &analyzer);
108                if !diagnostics.is_empty() {
109                    // Return the first error with proper span
110                    return TokenStream::from(diagnostics.into_iter().next().unwrap().write_errors());
111                }
112            }
113            
114            // Generate struct implementation
115            codegen::generate_struct_impl(
116                &merge_input.ident,
117                &merge_input.generics,
118                &fields,
119                struct_policy.as_ref(),
120                merge_input.generics.where_clause.as_ref(),
121            )
122        }
123        darling::ast::Data::Enum(_) => {
124            // Generate enum implementation
125            codegen::generate_enum_impl(
126                &merge_input.ident,
127                &merge_input.generics,
128                merge_input.generics.where_clause.as_ref(),
129            )
130        }
131    };
132    
133    TokenStream::from(output)
134}