1use darling::{FromDeriveInput, FromField, FromMeta};
18use proc_macro::TokenStream;
19use quote::{format_ident, quote};
20use syn::{DeriveInput, ItemFn, Type, parse_macro_input};
21
22#[derive(Debug, FromMeta)]
24struct CapabilityArgs {
25 #[darling(default)]
27 module: Option<String>,
28 #[darling(default)]
30 id: Option<String>,
31 #[darling(default)]
33 display_name: Option<String>,
34 #[darling(default)]
36 description: Option<String>,
37 #[darling(default)]
39 side_effects: bool,
40 #[darling(default)]
42 idempotent: Option<bool>,
43 #[darling(default)]
45 rate_limited: bool,
46
47 #[darling(default)]
51 module_display_name: Option<String>,
52 #[darling(default)]
54 module_description: Option<String>,
55 #[darling(default)]
57 module_has_side_effects: Option<bool>,
58 #[darling(default)]
60 module_supports_connections: Option<bool>,
61 #[darling(default)]
63 module_integration_ids: Option<String>,
64 #[darling(default)]
66 module_secure: Option<bool>,
67}
68
69#[derive(Debug, FromField)]
71#[darling(attributes(field), forward_attrs(serde))]
72struct InputFieldArgs {
73 ident: Option<syn::Ident>,
74 ty: syn::Type,
75 attrs: Vec<syn::Attribute>,
77 #[darling(default)]
78 display_name: Option<String>,
79 #[darling(default)]
80 description: Option<String>,
81 #[darling(default)]
82 example: Option<String>,
83 #[darling(default)]
84 default: Option<String>,
85 #[darling(default)]
87 skip: bool,
88 #[darling(default)]
90 enum_type: Option<String>,
91}
92
93#[derive(Debug, FromDeriveInput)]
95#[darling(attributes(capability_input))]
96struct InputContainerArgs {
97 ident: syn::Ident,
98 data: darling::ast::Data<(), InputFieldArgs>,
99 #[darling(default)]
100 display_name: Option<String>,
101 #[darling(default)]
102 description: Option<String>,
103}
104
105#[derive(Debug, FromField)]
107#[darling(attributes(field))]
108struct OutputFieldArgs {
109 ident: Option<syn::Ident>,
110 ty: syn::Type,
111 #[darling(default)]
112 display_name: Option<String>,
113 #[darling(default)]
114 description: Option<String>,
115 #[darling(default)]
116 example: Option<String>,
117}
118
119#[derive(Debug, FromDeriveInput)]
121#[darling(attributes(capability_output))]
122struct OutputContainerArgs {
123 ident: syn::Ident,
124 data: darling::ast::Data<(), OutputFieldArgs>,
125 #[darling(default)]
126 display_name: Option<String>,
127 #[darling(default)]
128 description: Option<String>,
129}
130
131#[proc_macro_attribute]
146pub fn capability(attr: TokenStream, item: TokenStream) -> TokenStream {
147 let args = match darling::ast::NestedMeta::parse_meta_list(attr.into()) {
148 Ok(v) => v,
149 Err(e) => return TokenStream::from(e.into_compile_error()),
150 };
151
152 let args = match CapabilityArgs::from_list(&args) {
153 Ok(v) => v,
154 Err(e) => return TokenStream::from(e.write_errors()),
155 };
156
157 let input_fn = parse_macro_input!(item as ItemFn);
158 let fn_name = &input_fn.sig.ident;
159 let fn_name_str = fn_name.to_string();
160
161 let capability_id = args.id.unwrap_or_else(|| fn_name_str.replace('_', "-"));
163
164 let input_type = input_fn
166 .sig
167 .inputs
168 .iter()
169 .find_map(|arg| {
170 if let syn::FnArg::Typed(pat_type) = arg {
171 if let Type::Path(type_path) = &*pat_type.ty {
172 return type_path.path.segments.last().map(|s| s.ident.to_string());
173 }
174 }
175 None
176 })
177 .unwrap_or_else(|| "Unknown".to_string());
178
179 let output_type = extract_result_ok_type(&input_fn.sig.output);
181
182 let display_name = args.display_name;
183 let description = args.description;
184 let side_effects = args.side_effects;
185 let idempotent = args.idempotent.unwrap_or(!side_effects);
186 let rate_limited = args.rate_limited;
187 let module = args.module;
188
189 let meta_ident = format_ident!("__CAPABILITY_META_{}", fn_name.to_string().to_uppercase());
191 let executor_ident = format_ident!(
192 "__CAPABILITY_EXECUTOR_{}",
193 fn_name.to_string().to_uppercase()
194 );
195 let executor_fn_ident = format_ident!("__executor_{}", fn_name);
196
197 let display_name_token = option_to_tokens(&display_name);
198 let description_token = option_to_tokens(&description);
199 let module_token = option_to_tokens(&module);
200
201 let module_str = module.clone().unwrap_or_else(|| "unknown".to_string());
203
204 let input_type_ident = format_ident!("{}", input_type);
206
207 let module_registration =
209 if let (Some(module_id), Some(mod_display_name)) = (&module, &args.module_display_name) {
210 let module_meta_ident = format_ident!(
211 "__AGENT_MODULE_META_{}_{}",
212 module_id.to_uppercase().replace('-', "_"),
213 fn_name.to_string().to_uppercase()
214 );
215
216 let mod_description = args
217 .module_description
218 .clone()
219 .unwrap_or_else(|| format!("{} agent module", mod_display_name));
220 let mod_has_side_effects = args.module_has_side_effects.unwrap_or(false);
221 let mod_supports_connections = args.module_supports_connections.unwrap_or(false);
222 let mod_secure = args.module_secure.unwrap_or(false);
223
224 let integration_ids_tokens = if let Some(ref ids_str) = args.module_integration_ids {
226 let ids: Vec<&str> = ids_str
227 .split(',')
228 .map(|s| s.trim())
229 .filter(|s| !s.is_empty())
230 .collect();
231 quote! { &[#(#ids),*] }
232 } else {
233 quote! { &[] }
234 };
235
236 Some(quote! {
237 #[allow(non_upper_case_globals)]
238 #[doc(hidden)]
239 pub static #module_meta_ident: runtara_dsl::agent_meta::AgentModuleConfig =
240 runtara_dsl::agent_meta::AgentModuleConfig {
241 id: #module_id,
242 name: #mod_display_name,
243 description: #mod_description,
244 has_side_effects: #mod_has_side_effects,
245 supports_connections: #mod_supports_connections,
246 integration_ids: #integration_ids_tokens,
247 secure: #mod_secure,
248 };
249
250 inventory::submit! {
251 &#module_meta_ident
252 }
253 })
254 } else {
255 None
256 };
257
258 let is_async = input_fn.sig.asyncness.is_some();
260
261 let executor_wrapper = if is_async {
263 quote! {
265 #[doc(hidden)]
266 fn #executor_fn_ident(input: serde_json::Value) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<serde_json::Value, String>> + Send>> {
267 Box::pin(async move {
268 let typed_input: #input_type_ident = serde_json::from_value(input)
269 .map_err(|e| format!("Invalid input for {}: {}", #capability_id, e))?;
270 let result = #fn_name(typed_input).await?;
271 serde_json::to_value(result)
272 .map_err(|e| format!("Failed to serialize result: {}", e))
273 })
274 }
275 }
276 } else {
277 quote! {
279 #[doc(hidden)]
280 fn #executor_fn_ident(input: serde_json::Value) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<serde_json::Value, String>> + Send>> {
281 Box::pin(async move {
282 tokio::task::spawn_blocking(move || {
283 let typed_input: #input_type_ident = serde_json::from_value(input)
284 .map_err(|e| format!("Invalid input for {}: {}", #capability_id, e))?;
285 let result = #fn_name(typed_input)?;
286 serde_json::to_value(result)
287 .map_err(|e| format!("Failed to serialize result: {}", e))
288 }).await.map_err(|e| format!("Task panicked: {}", e))?
289 })
290 }
291 }
292 };
293
294 let expanded = quote! {
295 #input_fn
296
297 #[allow(non_upper_case_globals)]
298 #[doc(hidden)]
299 pub static #meta_ident: runtara_dsl::agent_meta::CapabilityMeta = runtara_dsl::agent_meta::CapabilityMeta {
300 module: #module_token,
301 capability_id: #capability_id,
302 function_name: #fn_name_str,
303 input_type: #input_type,
304 output_type: #output_type,
305 display_name: #display_name_token,
306 description: #description_token,
307 has_side_effects: #side_effects,
308 is_idempotent: #idempotent,
309 rate_limited: #rate_limited,
310 };
311
312 inventory::submit! {
313 &#meta_ident
314 }
315
316 #executor_wrapper
317
318 #[allow(non_upper_case_globals)]
319 #[doc(hidden)]
320 pub static #executor_ident: runtara_dsl::agent_meta::CapabilityExecutor = runtara_dsl::agent_meta::CapabilityExecutor {
321 module: #module_str,
322 capability_id: #capability_id,
323 execute: #executor_fn_ident,
324 };
325
326 inventory::submit! {
327 &#executor_ident
328 }
329
330 #module_registration
331 };
332
333 TokenStream::from(expanded)
334}
335
336fn option_to_tokens(opt: &Option<String>) -> proc_macro2::TokenStream {
338 match opt {
339 Some(s) => quote! { Some(#s) },
340 None => quote! { None },
341 }
342}
343
344fn extract_result_ok_type(output: &syn::ReturnType) -> String {
346 if let syn::ReturnType::Type(_, ty) = output {
347 if let Type::Path(type_path) = &**ty {
348 if let Some(segment) = type_path.path.segments.first() {
349 if segment.ident == "Result" {
350 if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
351 if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
352 return type_to_string(inner_ty);
353 }
354 }
355 }
356 }
357 }
358 }
359 "Unknown".to_string()
360}
361
362fn type_to_string(ty: &Type) -> String {
364 match ty {
365 Type::Path(type_path) => {
366 let segments: Vec<String> = type_path
367 .path
368 .segments
369 .iter()
370 .map(|s| {
371 let ident = s.ident.to_string();
372 if let syn::PathArguments::AngleBracketed(args) = &s.arguments {
373 let inner: Vec<String> = args
374 .args
375 .iter()
376 .filter_map(|arg| {
377 if let syn::GenericArgument::Type(inner_ty) = arg {
378 Some(type_to_string(inner_ty))
379 } else {
380 None
381 }
382 })
383 .collect();
384 if !inner.is_empty() {
385 format!("{}<{}>", ident, inner.join(", "))
386 } else {
387 ident
388 }
389 } else {
390 ident
391 }
392 })
393 .collect();
394 segments.join("::")
395 }
396 Type::Tuple(tuple) if tuple.elems.is_empty() => "()".to_string(),
397 _ => "Unknown".to_string(),
398 }
399}
400
401#[proc_macro_derive(CapabilityInput, attributes(capability_input, field))]
415pub fn derive_capability_input(input: TokenStream) -> TokenStream {
416 let input = parse_macro_input!(input as DeriveInput);
417
418 let args = match InputContainerArgs::from_derive_input(&input) {
419 Ok(v) => v,
420 Err(e) => return TokenStream::from(e.write_errors()),
421 };
422
423 let struct_name = &args.ident;
424 let struct_name_str = struct_name.to_string();
425
426 let fields = match args.data {
427 darling::ast::Data::Struct(fields) => fields.fields,
428 _ => {
429 return TokenStream::from(
430 quote! { compile_error!("CapabilityInput can only be derived for structs"); },
431 );
432 }
433 };
434
435 let field_metas: Vec<_> = fields
436 .iter()
437 .filter(|f| !f.skip)
438 .filter(|f| {
439 f.ident.as_ref().map(|i| i.to_string()) != Some("connection_id".to_string())
441 })
442 .map(|f| {
443 let name = f.ident.as_ref().map(|i| i.to_string()).unwrap_or_default();
444 let type_str = type_to_string(&f.ty);
445 let (inner_type, is_option_type) = unwrap_option_type(&type_str);
446 let is_optional = is_option_type || f.default.is_some() || has_serde_default(&f.attrs);
448
449 let display_name_token = option_to_tokens(&f.display_name);
450 let description_token = option_to_tokens(&f.description);
451 let example_token = option_to_tokens(&f.example);
452 let default_token = option_to_tokens(&f.default);
453
454 let enum_values_fn_token = if let Some(ref enum_type) = f.enum_type {
455 let enum_ident = format_ident!("{}", enum_type);
456 quote! { Some(<#enum_ident as runtara_dsl::agent_meta::EnumVariants>::variant_names) }
457 } else {
458 quote! { None }
459 };
460
461 quote! {
462 runtara_dsl::agent_meta::InputFieldMeta {
463 name: #name,
464 type_name: #inner_type,
465 is_optional: #is_optional,
466 display_name: #display_name_token,
467 description: #description_token,
468 example: #example_token,
469 default_value: #default_token,
470 enum_values_fn: #enum_values_fn_token,
471 }
472 }
473 })
474 .collect();
475
476 let container_display_name = option_to_tokens(&args.display_name);
477 let container_description = option_to_tokens(&args.description);
478
479 let meta_ident = format_ident!("__INPUT_META_{}", struct_name);
480
481 let expanded = quote! {
482 #[allow(non_upper_case_globals)]
483 #[doc(hidden)]
484 pub static #meta_ident: runtara_dsl::agent_meta::InputTypeMeta = runtara_dsl::agent_meta::InputTypeMeta {
485 type_name: #struct_name_str,
486 display_name: #container_display_name,
487 description: #container_description,
488 fields: &[#(#field_metas),*],
489 };
490
491 inventory::submit! {
492 &#meta_ident
493 }
494 };
495
496 TokenStream::from(expanded)
497}
498
499#[derive(Debug, FromField)]
505#[darling(attributes(field), forward_attrs(serde))]
506struct ConnectionFieldArgs {
507 ident: Option<syn::Ident>,
508 ty: syn::Type,
509 attrs: Vec<syn::Attribute>,
511 #[darling(default)]
512 display_name: Option<String>,
513 #[darling(default)]
514 description: Option<String>,
515 #[darling(default)]
516 placeholder: Option<String>,
517 #[darling(default)]
518 default: Option<String>,
519 #[darling(default)]
521 secret: bool,
522}
523
524#[derive(Debug, FromDeriveInput)]
526#[darling(attributes(connection))]
527struct ConnectionContainerArgs {
528 ident: syn::Ident,
529 data: darling::ast::Data<(), ConnectionFieldArgs>,
530 integration_id: String,
532 #[darling(default)]
534 display_name: Option<String>,
535 #[darling(default)]
537 description: Option<String>,
538 #[darling(default)]
540 category: Option<String>,
541}
542
543#[proc_macro_derive(ConnectionParams, attributes(connection, field))]
565pub fn derive_connection_params(input: TokenStream) -> TokenStream {
566 let input = parse_macro_input!(input as DeriveInput);
567
568 let args = match ConnectionContainerArgs::from_derive_input(&input) {
569 Ok(v) => v,
570 Err(e) => return TokenStream::from(e.write_errors()),
571 };
572
573 let struct_name = &args.ident;
574 let integration_id = &args.integration_id;
575
576 let fields = match args.data {
577 darling::ast::Data::Struct(fields) => fields.fields,
578 _ => {
579 return TokenStream::from(
580 quote! { compile_error!("ConnectionParams can only be derived for structs"); },
581 );
582 }
583 };
584
585 let field_metas: Vec<_> = fields
586 .iter()
587 .map(|f| {
588 let name = f.ident.as_ref().map(|i| i.to_string()).unwrap_or_default();
589 let type_str = type_to_string(&f.ty);
590 let (inner_type, is_option_type) = unwrap_option_type(&type_str);
591 let is_optional = is_option_type || f.default.is_some() || has_serde_default(&f.attrs);
593
594 let display_name_token = option_to_tokens(&f.display_name);
595 let description_token = option_to_tokens(&f.description);
596 let placeholder_token = option_to_tokens(&f.placeholder);
597 let default_token = option_to_tokens(&f.default);
598 let is_secret = f.secret;
599
600 quote! {
601 runtara_dsl::agent_meta::ConnectionFieldMeta {
602 name: #name,
603 type_name: #inner_type,
604 is_optional: #is_optional,
605 display_name: #display_name_token,
606 description: #description_token,
607 placeholder: #placeholder_token,
608 default_value: #default_token,
609 is_secret: #is_secret,
610 }
611 }
612 })
613 .collect();
614
615 let display_name = args.display_name.unwrap_or_else(|| {
617 integration_id
619 .split('_')
620 .map(|s| {
621 let mut c = s.chars();
622 match c.next() {
623 None => String::new(),
624 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
625 }
626 })
627 .collect::<Vec<_>>()
628 .join(" ")
629 });
630
631 let description_token = option_to_tokens(&args.description);
632 let category_token = option_to_tokens(&args.category);
633
634 let meta_ident = format_ident!("__CONNECTION_META_{}", struct_name);
635
636 let expanded = quote! {
637 #[allow(non_upper_case_globals)]
638 #[doc(hidden)]
639 pub static #meta_ident: runtara_dsl::agent_meta::ConnectionTypeMeta = runtara_dsl::agent_meta::ConnectionTypeMeta {
640 integration_id: #integration_id,
641 display_name: #display_name,
642 description: #description_token,
643 category: #category_token,
644 fields: &[#(#field_metas),*],
645 };
646
647 inventory::submit! {
648 &#meta_ident
649 }
650 };
651
652 TokenStream::from(expanded)
653}
654
655fn unwrap_option_type(type_str: &str) -> (String, bool) {
657 if type_str.starts_with("Option<") && type_str.ends_with('>') {
658 let inner = type_str
659 .strip_prefix("Option<")
660 .unwrap()
661 .strip_suffix('>')
662 .unwrap();
663 (inner.to_string(), true)
664 } else {
665 (type_str.to_string(), false)
666 }
667}
668
669fn has_serde_default(attrs: &[syn::Attribute]) -> bool {
671 for attr in attrs {
672 if attr.path().is_ident("serde") {
673 let mut found_default = false;
674 let _ = attr.parse_nested_meta(|meta| {
675 if meta.path.is_ident("default") {
676 found_default = true;
677 }
678 Ok(())
679 });
680 if found_default {
681 return true;
682 }
683 }
684 }
685 false
686}
687
688fn analyze_type_for_nesting(type_str: &str) -> (bool, Option<String>, Option<String>) {
691 let mut is_nullable = false;
692 let mut working_type = type_str.to_string();
693
694 if let Some(inner) = working_type
696 .strip_prefix("Option<")
697 .and_then(|s| s.strip_suffix('>'))
698 {
699 is_nullable = true;
700 working_type = inner.to_string();
701 }
702
703 if let Some(inner) = working_type
705 .strip_prefix("Vec<")
706 .and_then(|s| s.strip_suffix('>'))
707 {
708 let items_type = inner.to_string();
710 return (is_nullable, Some(items_type), None);
711 }
712
713 if working_type.starts_with("HashMap<")
716 || working_type.starts_with("BTreeMap<")
717 || working_type.contains("::HashMap<")
718 || working_type.contains("::BTreeMap<")
719 {
720 return (is_nullable, None, None);
721 }
722
723 let primitives = [
725 "()", "bool", "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64",
726 "u128", "usize", "f32", "f64", "String", "Value",
727 ];
728
729 if primitives.contains(&working_type.as_str()) {
730 return (is_nullable, None, None);
731 }
732
733 if !working_type.contains("::")
737 && working_type
738 .chars()
739 .next()
740 .map(|c| c.is_uppercase())
741 .unwrap_or(false)
742 {
743 return (is_nullable, None, Some(working_type));
744 }
745
746 (is_nullable, None, None)
747}
748
749#[proc_macro_derive(CapabilityOutput, attributes(capability_output, field))]
751pub fn derive_capability_output(input: TokenStream) -> TokenStream {
752 let input = parse_macro_input!(input as DeriveInput);
753
754 let args = match OutputContainerArgs::from_derive_input(&input) {
755 Ok(v) => v,
756 Err(e) => return TokenStream::from(e.write_errors()),
757 };
758
759 let struct_name = &args.ident;
760 let struct_name_str = struct_name.to_string();
761
762 let fields = match args.data {
763 darling::ast::Data::Struct(fields) => fields.fields,
764 _ => {
765 return TokenStream::from(
766 quote! { compile_error!("CapabilityOutput can only be derived for structs"); },
767 );
768 }
769 };
770
771 let field_metas: Vec<_> = fields
772 .iter()
773 .map(|f| {
774 let name = f.ident.as_ref().map(|i| i.to_string()).unwrap_or_default();
775 let type_str = type_to_string(&f.ty);
776
777 let (is_nullable, items_type, nested_type) = analyze_type_for_nesting(&type_str);
779
780 let display_name_token = option_to_tokens(&f.display_name);
781 let description_token = option_to_tokens(&f.description);
782 let example_token = option_to_tokens(&f.example);
783 let items_type_token = option_to_tokens(&items_type);
784 let nested_type_token = option_to_tokens(&nested_type);
785
786 quote! {
787 runtara_dsl::agent_meta::OutputFieldMeta {
788 name: #name,
789 type_name: #type_str,
790 display_name: #display_name_token,
791 description: #description_token,
792 example: #example_token,
793 nullable: #is_nullable,
794 items_type_name: #items_type_token,
795 nested_type_name: #nested_type_token,
796 }
797 }
798 })
799 .collect();
800
801 let container_display_name = option_to_tokens(&args.display_name);
802 let container_description = option_to_tokens(&args.description);
803
804 let meta_ident = format_ident!("__OUTPUT_META_{}", struct_name);
805
806 let expanded = quote! {
807 #[allow(non_upper_case_globals)]
808 #[doc(hidden)]
809 pub static #meta_ident: runtara_dsl::agent_meta::OutputTypeMeta = runtara_dsl::agent_meta::OutputTypeMeta {
810 type_name: #struct_name_str,
811 display_name: #container_display_name,
812 description: #container_description,
813 fields: &[#(#field_metas),*],
814 };
815
816 inventory::submit! {
817 &#meta_ident
818 }
819 };
820
821 TokenStream::from(expanded)
822}
823
824#[derive(Debug, FromDeriveInput)]
830#[darling(attributes(step))]
831struct StepMetaArgs {
832 ident: syn::Ident,
833 #[darling(default)]
835 id: Option<String>,
836 #[darling(default)]
838 display_name: Option<String>,
839 #[darling(default)]
841 description: Option<String>,
842 #[darling(default)]
844 category: Option<String>,
845}
846
847#[proc_macro_derive(StepMeta, attributes(step))]
868pub fn derive_step_meta(input: TokenStream) -> TokenStream {
869 let input = parse_macro_input!(input as DeriveInput);
870
871 let args = match StepMetaArgs::from_derive_input(&input) {
872 Ok(v) => v,
873 Err(e) => return TokenStream::from(e.write_errors()),
874 };
875
876 let struct_name = &args.ident;
877 let struct_name_str = struct_name.to_string();
878
879 let step_id = args.id.unwrap_or_else(|| {
881 struct_name_str
882 .strip_suffix("Step")
883 .unwrap_or(&struct_name_str)
884 .to_string()
885 });
886
887 let display_name = args.display_name.unwrap_or_else(|| step_id.clone());
889
890 let description = args
892 .description
893 .unwrap_or_else(|| format!("{} step", step_id));
894
895 let category = args.category.unwrap_or_else(|| match step_id.as_str() {
897 "Agent" | "StartScenario" => "execution".to_string(),
898 _ => "control".to_string(),
899 });
900
901 let meta_ident = format_ident!("__STEP_META_{}", struct_name);
902 let schema_fn_ident = format_ident!("__step_schema_{}", struct_name.to_string().to_lowercase());
903
904 let expanded = quote! {
905 #[doc(hidden)]
906 fn #schema_fn_ident() -> schemars::schema::RootSchema {
907 schemars::schema_for!(#struct_name)
908 }
909
910 #[allow(non_upper_case_globals)]
911 #[doc(hidden)]
912 pub static #meta_ident: runtara_dsl::agent_meta::StepTypeMeta = runtara_dsl::agent_meta::StepTypeMeta {
913 id: #step_id,
914 display_name: #display_name,
915 description: #description,
916 category: #category,
917 schema_fn: #schema_fn_ident,
918 };
919
920 inventory::submit! {
921 &#meta_ident
922 }
923 };
924
925 TokenStream::from(expanded)
926}
927
928#[cfg(test)]
929mod tests {
930 use super::*;
931 use syn::parse_quote;
932
933 #[test]
938 fn test_unwrap_option_type_non_optional() {
939 let (inner, is_optional) = unwrap_option_type("String");
940 assert_eq!(inner, "String");
941 assert!(!is_optional);
942 }
943
944 #[test]
945 fn test_unwrap_option_type_simple_option() {
946 let (inner, is_optional) = unwrap_option_type("Option<String>");
947 assert_eq!(inner, "String");
948 assert!(is_optional);
949 }
950
951 #[test]
952 fn test_unwrap_option_type_primitive() {
953 let (inner, is_optional) = unwrap_option_type("Option<i32>");
954 assert_eq!(inner, "i32");
955 assert!(is_optional);
956 }
957
958 #[test]
959 fn test_unwrap_option_type_complex_inner() {
960 let (inner, is_optional) = unwrap_option_type("Option<Vec<String>>");
961 assert_eq!(inner, "Vec<String>");
962 assert!(is_optional);
963 }
964
965 #[test]
966 fn test_unwrap_option_type_non_option_generic() {
967 let (inner, is_optional) = unwrap_option_type("Vec<String>");
968 assert_eq!(inner, "Vec<String>");
969 assert!(!is_optional);
970 }
971
972 #[test]
973 fn test_unwrap_option_type_empty_string() {
974 let (inner, is_optional) = unwrap_option_type("");
975 assert_eq!(inner, "");
976 assert!(!is_optional);
977 }
978
979 #[test]
984 fn test_type_to_string_simple_type() {
985 let ty: Type = parse_quote!(String);
986 assert_eq!(type_to_string(&ty), "String");
987 }
988
989 #[test]
990 fn test_type_to_string_primitive() {
991 let ty: Type = parse_quote!(i32);
992 assert_eq!(type_to_string(&ty), "i32");
993 }
994
995 #[test]
996 fn test_type_to_string_generic_single() {
997 let ty: Type = parse_quote!(Option<String>);
998 assert_eq!(type_to_string(&ty), "Option<String>");
999 }
1000
1001 #[test]
1002 fn test_type_to_string_generic_multiple() {
1003 let ty: Type = parse_quote!(HashMap<String, i32>);
1004 assert_eq!(type_to_string(&ty), "HashMap<String, i32>");
1005 }
1006
1007 #[test]
1008 fn test_type_to_string_vec() {
1009 let ty: Type = parse_quote!(Vec<u8>);
1010 assert_eq!(type_to_string(&ty), "Vec<u8>");
1011 }
1012
1013 #[test]
1014 fn test_type_to_string_nested_generics() {
1015 let ty: Type = parse_quote!(Option<Vec<String>>);
1016 assert_eq!(type_to_string(&ty), "Option<Vec<String>>");
1017 }
1018
1019 #[test]
1020 fn test_type_to_string_unit_type() {
1021 let ty: Type = parse_quote!(());
1022 assert_eq!(type_to_string(&ty), "()");
1023 }
1024
1025 #[test]
1026 fn test_type_to_string_path_type() {
1027 let ty: Type = parse_quote!(std::collections::HashMap<String, i32>);
1028 assert_eq!(
1029 type_to_string(&ty),
1030 "std::collections::HashMap<String, i32>"
1031 );
1032 }
1033
1034 #[test]
1035 fn test_type_to_string_result_type() {
1036 let ty: Type = parse_quote!(Result<String, Error>);
1037 assert_eq!(type_to_string(&ty), "Result<String, Error>");
1038 }
1039
1040 #[test]
1041 fn test_type_to_string_custom_type() {
1042 let ty: Type = parse_quote!(MyCustomInput);
1043 assert_eq!(type_to_string(&ty), "MyCustomInput");
1044 }
1045
1046 #[test]
1051 fn test_analyze_type_for_nesting_primitive() {
1052 let (nullable, items, nested) = analyze_type_for_nesting("String");
1053 assert!(!nullable);
1054 assert!(items.is_none());
1055 assert!(nested.is_none());
1056 }
1057
1058 #[test]
1059 fn test_analyze_type_for_nesting_i32() {
1060 let (nullable, items, nested) = analyze_type_for_nesting("i32");
1061 assert!(!nullable);
1062 assert!(items.is_none());
1063 assert!(nested.is_none());
1064 }
1065
1066 #[test]
1067 fn test_analyze_type_for_nesting_option_primitive() {
1068 let (nullable, items, nested) = analyze_type_for_nesting("Option<String>");
1069 assert!(nullable);
1070 assert!(items.is_none());
1071 assert!(nested.is_none());
1072 }
1073
1074 #[test]
1075 fn test_analyze_type_for_nesting_vec() {
1076 let (nullable, items, nested) = analyze_type_for_nesting("Vec<String>");
1077 assert!(!nullable);
1078 assert_eq!(items, Some("String".to_string()));
1079 assert!(nested.is_none());
1080 }
1081
1082 #[test]
1083 fn test_analyze_type_for_nesting_option_vec() {
1084 let (nullable, items, nested) = analyze_type_for_nesting("Option<Vec<i32>>");
1085 assert!(nullable);
1086 assert_eq!(items, Some("i32".to_string()));
1087 assert!(nested.is_none());
1088 }
1089
1090 #[test]
1091 fn test_analyze_type_for_nesting_hashmap() {
1092 let (nullable, items, nested) = analyze_type_for_nesting("HashMap<String, i32>");
1093 assert!(!nullable);
1094 assert!(items.is_none());
1095 assert!(nested.is_none());
1096 }
1097
1098 #[test]
1099 fn test_analyze_type_for_nesting_btreemap() {
1100 let (nullable, items, nested) = analyze_type_for_nesting("BTreeMap<String, Value>");
1101 assert!(!nullable);
1102 assert!(items.is_none());
1103 assert!(nested.is_none());
1104 }
1105
1106 #[test]
1107 fn test_analyze_type_for_nesting_std_hashmap() {
1108 let (nullable, items, nested) =
1109 analyze_type_for_nesting("std::collections::HashMap<String, i32>");
1110 assert!(!nullable);
1111 assert!(items.is_none());
1112 assert!(nested.is_none());
1113 }
1114
1115 #[test]
1116 fn test_analyze_type_for_nesting_custom_type() {
1117 let (nullable, items, nested) = analyze_type_for_nesting("MyCustomStruct");
1118 assert!(!nullable);
1119 assert!(items.is_none());
1120 assert_eq!(nested, Some("MyCustomStruct".to_string()));
1121 }
1122
1123 #[test]
1124 fn test_analyze_type_for_nesting_option_custom() {
1125 let (nullable, items, nested) = analyze_type_for_nesting("Option<AddressInfo>");
1126 assert!(nullable);
1127 assert!(items.is_none());
1128 assert_eq!(nested, Some("AddressInfo".to_string()));
1129 }
1130
1131 #[test]
1132 fn test_analyze_type_for_nesting_unit_type() {
1133 let (nullable, items, nested) = analyze_type_for_nesting("()");
1134 assert!(!nullable);
1135 assert!(items.is_none());
1136 assert!(nested.is_none());
1137 }
1138
1139 #[test]
1140 fn test_analyze_type_for_nesting_value() {
1141 let (nullable, items, nested) = analyze_type_for_nesting("Value");
1142 assert!(!nullable);
1143 assert!(items.is_none());
1144 assert!(nested.is_none());
1145 }
1146
1147 #[test]
1148 fn test_analyze_type_for_nesting_lowercase_custom() {
1149 let (nullable, items, nested) = analyze_type_for_nesting("lowercase_type");
1151 assert!(!nullable);
1152 assert!(items.is_none());
1153 assert!(nested.is_none());
1154 }
1155
1156 #[test]
1161 fn test_option_to_tokens_some() {
1162 let opt = Some("test value".to_string());
1163 let tokens = option_to_tokens(&opt);
1164 let code = tokens.to_string();
1165 assert!(code.contains("Some"));
1166 assert!(code.contains("test value"));
1167 }
1168
1169 #[test]
1170 fn test_option_to_tokens_none() {
1171 let opt: Option<String> = None;
1172 let tokens = option_to_tokens(&opt);
1173 let code = tokens.to_string();
1174 assert_eq!(code, "None");
1175 }
1176
1177 #[test]
1178 fn test_option_to_tokens_empty_string() {
1179 let opt = Some("".to_string());
1180 let tokens = option_to_tokens(&opt);
1181 let code = tokens.to_string();
1182 assert!(code.contains("Some"));
1183 }
1184
1185 #[test]
1186 fn test_option_to_tokens_special_chars() {
1187 let opt = Some("value with \"quotes\" and 'apostrophes'".to_string());
1188 let tokens = option_to_tokens(&opt);
1189 let code = tokens.to_string();
1190 assert!(code.contains("Some"));
1191 }
1192
1193 #[test]
1198 fn test_extract_result_ok_type_simple() {
1199 let output: syn::ReturnType = parse_quote!(-> Result<String, Error>);
1200 assert_eq!(extract_result_ok_type(&output), "String");
1201 }
1202
1203 #[test]
1204 fn test_extract_result_ok_type_custom() {
1205 let output: syn::ReturnType = parse_quote!(-> Result<MyOutput, String>);
1206 assert_eq!(extract_result_ok_type(&output), "MyOutput");
1207 }
1208
1209 #[test]
1210 fn test_extract_result_ok_type_unit() {
1211 let output: syn::ReturnType = parse_quote!(-> Result<(), Error>);
1212 assert_eq!(extract_result_ok_type(&output), "()");
1213 }
1214
1215 #[test]
1216 fn test_extract_result_ok_type_generic() {
1217 let output: syn::ReturnType = parse_quote!(-> Result<Vec<String>, Error>);
1218 assert_eq!(extract_result_ok_type(&output), "Vec<String>");
1219 }
1220
1221 #[test]
1222 fn test_extract_result_ok_type_no_return() {
1223 let output: syn::ReturnType = syn::ReturnType::Default;
1224 assert_eq!(extract_result_ok_type(&output), "Unknown");
1225 }
1226
1227 #[test]
1228 fn test_extract_result_ok_type_non_result() {
1229 let output: syn::ReturnType = parse_quote!(-> String);
1230 assert_eq!(extract_result_ok_type(&output), "Unknown");
1231 }
1232
1233 #[test]
1234 fn test_extract_result_ok_type_option() {
1235 let output: syn::ReturnType = parse_quote!(-> Result<Option<String>, Error>);
1236 assert_eq!(extract_result_ok_type(&output), "Option<String>");
1237 }
1238
1239 #[test]
1244 fn test_has_serde_default_true() {
1245 let attr: syn::Attribute = parse_quote!(#[serde(default)]);
1246 assert!(has_serde_default(&[attr]));
1247 }
1248
1249 #[test]
1250 fn test_has_serde_default_with_value() {
1251 let attr: syn::Attribute = parse_quote!(#[serde(default = "default_value")]);
1252 assert!(has_serde_default(&[attr]));
1253 }
1254
1255 #[test]
1256 fn test_has_serde_default_other_serde_attr() {
1257 let attr: syn::Attribute = parse_quote!(#[serde(rename = "other_name")]);
1258 assert!(!has_serde_default(&[attr]));
1259 }
1260
1261 #[test]
1262 fn test_has_serde_default_non_serde() {
1263 let attr: syn::Attribute = parse_quote!(#[field(display_name = "Test")]);
1264 assert!(!has_serde_default(&[attr]));
1265 }
1266
1267 #[test]
1268 fn test_has_serde_default_empty() {
1269 assert!(!has_serde_default(&[]));
1270 }
1271
1272 #[test]
1273 fn test_has_serde_default_multiple_attrs() {
1274 let attr1: syn::Attribute = parse_quote!(#[field(display_name = "Test")]);
1275 let attr2: syn::Attribute = parse_quote!(#[serde(default)]);
1276 assert!(has_serde_default(&[attr1, attr2]));
1277 }
1278
1279 #[test]
1280 fn test_has_serde_default_multiple_nested_default_first() {
1281 let attr: syn::Attribute = parse_quote!(#[serde(default, rename = "foo")]);
1283 assert!(has_serde_default(&[attr]));
1284 }
1285
1286 }