ai_json_template_derive/
lib.rs1#![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#[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 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 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 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 Data::Enum(data_enum) => {
148 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 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#[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 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 syn::Data::Struct(ds) => {
297
298 if let syn::Fields::Named(ref named_fields) = ds.fields {
299 let justified_struct = generate_justified_structs_for_named(ty_ident, named_fields, span);
301 out.extend(justified_struct);
302
303 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 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 syn::Data::Enum(data_enum) => {
330
331 let justified_enum = generate_enum_justified(ty_ident, data_enum, span);
333 out.extend(justified_enum);
334
335 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 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;