ai_json_template_derive/
lib.rs

1// ---------------- [ File: ai-json-template-derive/src/lib.rs ]
2//#![allow(dead_code)]
3#![feature(box_patterns)]
4#![allow(unused_imports)]
5#![allow(unused_variables)]
6#[macro_use] mod imports; use imports::*;
7
8xp!{build_enum_variant_expr_with_justification}
9xp!{build_enum_variant_fields_map_with_justification}
10xp!{build_from_arm_for_named}
11xp!{build_from_arm_for_unit_variant}
12xp!{build_hashmap_schema}
13xp!{build_named_field_child_schema_expr}
14xp!{build_named_field_just_conf_placeholders}
15xp!{build_top_level_justification_fields_for_variant}
16xp!{classify_field_type}
17xp!{classify_field_type_for_child}
18xp!{comma_separated_expression}
19xp!{emit_schema_for_bool}
20xp!{emit_schema_for_fallback_nested}
21xp!{emit_schema_for_hashmap}
22xp!{emit_schema_for_number}
23xp!{emit_schema_for_string}
24xp!{emit_schema_for_type}
25xp!{emit_schema_for_vec}
26xp!{expand_enum_with_justification}
27xp!{expand_named_struct_with_justification}
28xp!{extract_hashmap_inner}
29xp!{extract_option_inner}
30xp!{extract_vec_inner}
31xp!{find_default_variant}
32xp!{flattened_field_result}
33xp!{gather_doc_comments}
34xp!{gather_schemas_and_placeholders_for_named_fields}
35xp!{generate_enum_justified}
36xp!{generate_justified_structs_for_named}
37xp!{generate_manual_default_for_justified_enum}
38xp!{generate_manual_default_for_justified_named_struct}
39xp!{generate_reverse_from_impl_for_enum_with_justification}
40xp!{generate_reverse_from_impl_for_named_with_justification}
41xp!{generate_to_template_with_justification_for_enum}
42xp!{generate_to_template_with_justification_for_named}
43xp!{is_builtin_scalar}
44xp!{is_justification_enabled}
45xp!{is_leaf_type}
46xp!{is_numeric}
47xp!{justified_type}
48xp!{parse_doc_expr}
49xp!{resolve_map_key_type}
50xp!{top_level_justification_result}
51xp!{unnamed_variant_expansion}
52
53/// This new implementation supports:
54///
55/// - Named structs (as before),
56/// - Enums with any mix of:
57///    - Unit variants,
58///    - Struct variants (with named fields),
59///    - Tuple variants (with unnamed fields).
60///
61/// It generates a template describing each variant and its fields. Where those fields are
62/// themselves `AiJsonTemplate`, the macro nests that template, just like with structs.
63/// 
64/// Please integrate this **entire function** as-is (and make sure the other helper modules
65/// like `gather_doc_comments` and `classify_field_type` are still in scope).
66///
67#[proc_macro_derive(AiJsonTemplate)]
68pub fn derive_ai_json_template(input: TokenStream) -> TokenStream {
69    tracing::trace!("Entering derive_ai_json_template macro.");
70
71    let ast = parse_macro_input!(input as DeriveInput);
72    let span  = ast.span();
73    let type_ident = &ast.ident;
74    let type_name_str = type_ident.to_string();
75    tracing::trace!("Analyzing type: {}", type_name_str);
76
77    // Gather doc comments from the container itself
78    let container_docs_vec = gather_doc_comments(&ast.attrs);
79    let container_docs_str = container_docs_vec.join("\n");
80
81    match &ast.data {
82        // ----------------- Named Struct Path -----------------
83        Data::Struct(ds) => {
84            match &ds.fields {
85                Fields::Named(named_fields) => {
86                    let mut field_inits = Vec::new();
87                    for field in &named_fields.named {
88                        let field_ident = match &field.ident {
89                            Some(id) => id,
90                            None => {
91                                let err = syn::Error::new(
92                                    field.span(),
93                                    "Unnamed fields are not supported by AiJsonTemplate for named structs."
94                                );
95                                return err.to_compile_error().into();
96                            }
97                        };
98
99                        let field_name_str = field_ident.to_string();
100                        let field_docs = gather_doc_comments(&field.attrs).join("\n");
101                        let ty = &field.ty;
102
103                        if let Some(expr) = classify_field_type(ty, &field_docs) {
104                            field_inits.push(quote! {
105                                map.insert(#field_name_str.to_string(), #expr);
106                            });
107                        } else {
108                            let type_q = quote!(#ty).to_string();
109                            let err_msg = format!("Unsupported field type for AiJsonTemplate: {}", type_q);
110                            let err = syn::Error::new(ty.span(), &err_msg);
111                            return err.to_compile_error().into();
112                        }
113                    }
114
115                    let expanded = quote! {
116                        impl AiJsonTemplate for #type_ident {
117                            fn to_template() -> serde_json::Value {
118                                tracing::trace!("AiJsonTemplate::to_template for named struct {}", #type_name_str);
119
120                                let mut root = serde_json::Map::new();
121                                // Include our container docs plus disclaimers
122                                root.insert("struct_docs".to_string(), serde_json::Value::String(#container_docs_str.to_string()));
123                                root.insert("struct_name".to_string(), serde_json::Value::String(#type_name_str.to_string()));
124                                root.insert("type".to_string(), serde_json::Value::String("struct".to_string()));
125
126                                let mut map = serde_json::Map::new();
127                                #(#field_inits)*
128
129                                root.insert("fields".to_string(), serde_json::Value::Object(map));
130                                serde_json::Value::Object(root)
131                            }
132                        }
133                    };
134                    expanded.into()
135                },
136                _ => {
137                    let err = syn::Error::new(
138                        span,
139                        "AiJsonTemplate derive only supports named fields for structs. Tuple/unnamed not supported here."
140                    );
141                    err.to_compile_error().into()
142                }
143            }
144        },
145
146        // ----------------- Enum Path -----------------
147        Data::Enum(data_enum) => {
148            // We'll gather a snippet for each variant
149            let mut variant_exprs = Vec::new();
150            for var in &data_enum.variants {
151                let var_name_str = var.ident.to_string();
152                let var_docs = gather_doc_comments(&var.attrs).join("\n");
153
154                match &var.fields {
155                    Fields::Unit => {
156                        let expr = quote! {
157                            {
158                                let mut variant_map = serde_json::Map::new();
159                                variant_map.insert("variant_name".to_string(), serde_json::Value::String(#var_name_str.to_string()));
160                                variant_map.insert("variant_docs".to_string(), serde_json::Value::String(#var_docs.to_string()));
161                                variant_map.insert("variant_type".to_string(), serde_json::Value::String("unit_variant".to_string()));
162                                serde_json::Value::Object(variant_map)
163                            }
164                        };
165                        variant_exprs.push(expr);
166                    }
167                    Fields::Named(named_fields) => {
168                        let mut field_inits = Vec::new();
169                        for field in &named_fields.named {
170                            let field_ident = field.ident.as_ref().unwrap();
171                            let field_name_str = field_ident.to_string();
172                            let fd = gather_doc_comments(&field.attrs).join("\n");
173                            let ty = &field.ty;
174
175                            if let Some(expr) = classify_field_type(ty, &fd) {
176                                field_inits.push(quote! {
177                                    map.insert(#field_name_str.to_string(), #expr);
178                                });
179                            } else {
180                                let type_q = quote!(#ty).to_string();
181                                let err_msg = format!("Unsupported field type in enum variant {}: {}", var_name_str, type_q);
182                                let err = syn::Error::new(ty.span(), &err_msg);
183                                return err.to_compile_error().into();
184                            }
185                        }
186
187                        let expr = quote! {
188                            {
189                                let mut variant_map = serde_json::Map::new();
190                                variant_map.insert("variant_name".to_string(), serde_json::Value::String(#var_name_str.to_string()));
191                                variant_map.insert("variant_docs".to_string(), serde_json::Value::String(#var_docs.to_string()));
192                                variant_map.insert("variant_type".to_string(), serde_json::Value::String("struct_variant".to_string()));
193
194                                let mut map = serde_json::Map::new();
195                                #(#field_inits)*
196
197                                variant_map.insert("fields".to_string(), serde_json::Value::Object(map));
198                                serde_json::Value::Object(variant_map)
199                            }
200                        };
201                        variant_exprs.push(expr);
202                    }
203                    Fields::Unnamed(unnamed_fields) => {
204                        let mut field_inits = Vec::new();
205                        for (i, field) in unnamed_fields.unnamed.iter().enumerate() {
206                            let field_key = format!("field_{}", i);
207                            let fd = gather_doc_comments(&field.attrs).join("\n");
208                            let ty = &field.ty;
209
210                            if let Some(expr) = classify_field_type(ty, &fd) {
211                                field_inits.push(quote! {
212                                    map.insert(#field_key.to_string(), #expr);
213                                });
214                            } else {
215                                let type_q = quote!(#ty).to_string();
216                                let err_msg = format!("Unsupported field type in tuple variant {}: {}", var_name_str, type_q);
217                                let err = syn::Error::new(ty.span(), &err_msg);
218                                return err.to_compile_error().into();
219                            }
220                        }
221
222                        let expr = quote! {
223                            {
224                                let mut variant_map = serde_json::Map::new();
225                                variant_map.insert("variant_name".to_string(), serde_json::Value::String(#var_name_str.to_string()));
226                                variant_map.insert("variant_docs".to_string(), serde_json::Value::String(#var_docs.to_string()));
227                                variant_map.insert("variant_type".to_string(), serde_json::Value::String("tuple_variant".to_string()));
228
229                                let mut map = serde_json::Map::new();
230                                #(#field_inits)*
231
232                                variant_map.insert("fields".to_string(), serde_json::Value::Object(map));
233                                serde_json::Value::Object(variant_map)
234                            }
235                        };
236                        variant_exprs.push(expr);
237                    }
238                }
239            }
240
241            let expanded = quote! {
242                impl AiJsonTemplate for #type_ident {
243                    fn to_template() -> serde_json::Value {
244                        tracing::trace!("AiJsonTemplate::to_template for enum {}", #type_name_str);
245
246                        let mut root = serde_json::Map::new();
247
248                        root.insert("enum_docs".to_string(), serde_json::Value::String(#container_docs_str.to_string()));
249                        root.insert("enum_name".to_string(), serde_json::Value::String(#type_name_str.to_string()));
250                        root.insert("type".to_string(), serde_json::Value::String("complex_enum".to_string()));
251
252                        let variants_array = serde_json::Value::Array(vec![
253                            #(#variant_exprs),*
254                        ]);
255                        root.insert("variants".to_string(), variants_array);
256
257                        serde_json::Value::Object(root)
258                    }
259                }
260            };
261            expanded.into()
262        },
263
264        // ----------------- Union => not supported
265        Data::Union(_) => {
266            let err = syn::Error::new(
267                span,
268                "AiJsonTemplate derive does not support unions."
269            );
270            err.to_compile_error().into()
271        }
272    }
273}
274
275/// The main entrypoint for `#[derive(AiJsonTemplateWithJustification)]`.
276/// We generate (a) the typed justification structs/enums, and
277/// (b) the *FlatJustified* expansions if desired by the test suite.
278#[proc_macro_derive(AiJsonTemplateWithJustification, attributes(doc, justify, justify_inner))]
279pub fn derive_ai_json_template_with_justification(
280    input: proc_macro::TokenStream
281) -> proc_macro::TokenStream {
282
283    let ast  = syn::parse_macro_input!(input as syn::DeriveInput);
284    let span = ast.span();
285
286    // We'll build up the final expansions in `out`
287    let mut out = proc_macro2::TokenStream::new();
288
289    let ty_ident           = &ast.ident;
290    let container_docs     = gather_doc_comments(&ast.attrs);
291    let _container_doc_str = container_docs.join("\n");
292
293    match &ast.data {
294
295        // ==================== Named Struct ====================
296        syn::Data::Struct(ds) => {
297
298            if let syn::Fields::Named(ref named_fields) = ds.fields {
299                // (a) typed expansions
300                let justified_struct = generate_justified_structs_for_named(ty_ident, named_fields, span);
301                out.extend(justified_struct);
302
303                // Optionally generate a specialized "to_template_with_justification()"—you might already do so.
304                let to_tpl_ts = generate_to_template_with_justification_for_named(
305                    ty_ident,
306                    named_fields,
307                    &_container_doc_str
308                );
309                out.extend(to_tpl_ts);
310
311                let from_impl = generate_reverse_from_impl_for_named_with_justification(
312                    ty_ident,
313                    named_fields,
314                    span,
315                );
316                out.extend(from_impl);
317
318            } else {
319                // e.g. unit or tuple struct => produce an error or do something else
320                let err = syn::Error::new(
321                    span,
322                    "AiJsonTemplateWithJustification only supports named structs"
323                );
324                out.extend(err.to_compile_error());
325            }
326        }
327
328        // ==================== Enum ====================
329        syn::Data::Enum(data_enum) => {
330
331            // (a) typed expansions for justification/conf + JustifiedEnum
332            let justified_enum = generate_enum_justified(ty_ident, data_enum, span);
333            out.extend(justified_enum);
334
335            // Optionally generate a specialized "to_template_with_justification_for_enum(...)"
336            let enum_tpl_ts = generate_to_template_with_justification_for_enum(
337                ty_ident,
338                data_enum,
339                &_container_doc_str
340            );
341            out.extend(enum_tpl_ts);
342
343            let from_impl = generate_reverse_from_impl_for_enum_with_justification(
344                ty_ident,
345                data_enum,
346                span,
347            );
348            out.extend(from_impl);
349        }
350
351        // ==================== Union => not supported ====================
352        syn::Data::Union(_) => {
353            let e = syn::Error::new(span, "AiJsonTemplateWithJustification not supported on unions.");
354            out.extend(e.to_compile_error());
355        }
356    }
357
358    if DEBUG_FINAL_EXPANSION {
359
360        eprintln!("=== FINAL EXPANSION for {} ===\n{}", ty_ident, out.to_string());
361
362        assert_tokens_parse_ok(&out);
363    }
364
365    out.into()
366}
367
368const DEBUG_FINAL_EXPANSION: bool = false;