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}