1use proc_macro::TokenStream;
8use proc_macro2::TokenStream as TokenStream2;
9use quote::{format_ident, quote};
10use syn::{
11 Attribute, Data, DeriveInput, Expr, Fields, Ident, Lit, LitStr, Meta, Type, parse_macro_input,
12};
13
14#[proc_macro_derive(
32 Survey,
33 attributes(
34 ask,
35 mask,
36 multiline,
37 validate,
38 validate_fields,
39 min,
40 max,
41 prelude,
42 epilogue,
43 multiselect
44 )
45)]
46pub fn elicit(input: TokenStream) -> TokenStream {
47 let input = parse_macro_input!(input as DeriveInput);
48 implement_survey(&input)
49 .unwrap_or_else(|err| err.to_compile_error())
50 .into()
51}
52
53fn implement_survey(input: &DeriveInput) -> syn::Result<TokenStream2> {
54 let name = &input.ident;
55 let builder_name = format_ident!("{}Builder", name);
56
57 let type_attrs = TypeAttrs::extract(&input.attrs)?;
59
60 let survey_fn = generate_survey_fn(input, &type_attrs)?;
62
63 let from_responses_fn = generate_from_responses_fn(input)?;
65
66 let validate_field_fn = generate_validate_field_fn(input)?;
68
69 let validate_all_fn = generate_validate_all_fn(input, &type_attrs)?;
71
72 let builder_impl = generate_builder(input, &builder_name)?;
74
75 let validator_checks = generate_validator_checks(input)?;
77
78 let field_accessors = generate_field_accessors(input)?;
80
81 let validation_context = generate_validation_context(input)?;
83
84 Ok(quote! {
85 #validator_checks
86
87 #validation_context
88
89 impl elicitor::Survey for #name {
90 fn survey() -> elicitor::SurveyDefinition {
91 #survey_fn
92 }
93
94 fn from_responses(responses: &elicitor::Responses) -> Self {
95 #from_responses_fn
96 }
97
98 fn validate_field(
99 value: &elicitor::ResponseValue,
100 responses: &elicitor::Responses,
101 path: &elicitor::ResponsePath,
102 ) -> Result<(), String> {
103 #validate_field_fn
104 }
105
106 fn validate_all(
107 responses: &elicitor::Responses,
108 ) -> std::collections::HashMap<elicitor::ResponsePath, String> {
109 #validate_all_fn
110 }
111 }
112
113 impl #name {
114 pub fn builder() -> #builder_name {
116 #builder_name::new()
117 }
118
119 #field_accessors
120 }
121
122 #builder_impl
123 })
124}
125
126struct TypeAttrs {
132 prelude: Option<String>,
133 epilogue: Option<String>,
134 validate: Option<Ident>,
135 validate_fields: Option<Ident>,
137}
138
139impl TypeAttrs {
140 fn extract(attrs: &[Attribute]) -> syn::Result<Self> {
141 let mut prelude = None;
142 let mut epilogue = None;
143 let mut validate = None;
144 let mut validate_fields = None;
145
146 for attr in attrs {
147 if attr.path().is_ident("prelude") {
148 prelude = Some(extract_string_attr(attr)?);
149 } else if attr.path().is_ident("epilogue") {
150 epilogue = Some(extract_string_attr(attr)?);
151 } else if attr.path().is_ident("validate") {
152 validate = Some(extract_ident_attr(attr)?);
153 } else if attr.path().is_ident("validate_fields") {
154 validate_fields = Some(extract_ident_attr(attr)?);
155 }
156 }
157
158 Ok(Self {
159 prelude,
160 epilogue,
161 validate,
162 validate_fields,
163 })
164 }
165}
166
167struct FieldAttrs {
169 ask: Option<String>,
170 mask: bool,
171 multiline: bool,
172 validate: Option<Ident>,
173 min: Option<i64>,
174 max: Option<i64>,
175 multiselect: bool,
176}
177
178impl FieldAttrs {
179 fn extract(attrs: &[Attribute]) -> syn::Result<Self> {
180 let mut ask = None;
181 let mut mask = false;
182 let mut multiline = false;
183 let mut validate = None;
184 let mut min = None;
185 let mut max = None;
186 let mut multiselect = false;
187
188 for attr in attrs {
189 if attr.path().is_ident("ask") {
190 ask = Some(extract_string_attr(attr)?);
191 } else if attr.path().is_ident("mask") {
192 mask = true;
193 } else if attr.path().is_ident("multiline") {
194 multiline = true;
195 } else if attr.path().is_ident("validate") {
196 validate = Some(extract_ident_attr(attr)?);
197 } else if attr.path().is_ident("min") {
198 min = Some(extract_int_attr(attr)?);
199 } else if attr.path().is_ident("max") {
200 max = Some(extract_int_attr(attr)?);
201 } else if attr.path().is_ident("multiselect") {
202 multiselect = true;
203 }
204 }
205
206 Ok(Self {
207 ask,
208 mask,
209 multiline,
210 validate,
211 min,
212 max,
213 multiselect,
214 })
215 }
216}
217
218fn extract_string_attr(attr: &Attribute) -> syn::Result<String> {
219 let meta = &attr.meta;
220 match meta {
221 Meta::List(list) => {
222 let lit: LitStr = list.parse_args()?;
223 Ok(lit.value())
224 }
225 _ => Err(syn::Error::new_spanned(
226 attr,
227 "expected #[attr(\"string\")]",
228 )),
229 }
230}
231
232fn extract_ident_attr(attr: &Attribute) -> syn::Result<Ident> {
233 let meta = &attr.meta;
234 match meta {
235 Meta::List(list) => {
236 if let Ok(lit) = list.parse_args::<LitStr>() {
238 return Ok(Ident::new(&lit.value(), lit.span()));
239 }
240 list.parse_args()
242 }
243 _ => Err(syn::Error::new_spanned(
244 attr,
245 "expected #[attr(identifier)] or #[attr(\"string\")]",
246 )),
247 }
248}
249
250fn extract_int_attr(attr: &Attribute) -> syn::Result<i64> {
251 let meta = &attr.meta;
252 match meta {
253 Meta::List(list) => {
254 let expr: Expr = list.parse_args()?;
255 match expr {
256 Expr::Lit(lit) => match &lit.lit {
257 Lit::Int(int) => int.base10_parse(),
258 _ => Err(syn::Error::new_spanned(lit, "expected integer literal")),
259 },
260 Expr::Unary(ref unary) => {
261 if matches!(unary.op, syn::UnOp::Neg(_))
262 && let Expr::Lit(ref lit) = *unary.expr
263 && let Lit::Int(int) = &lit.lit
264 {
265 let val: i64 = int.base10_parse()?;
266 return Ok(-val);
267 }
268 Err(syn::Error::new_spanned(expr, "expected integer literal"))
269 }
270 _ => Err(syn::Error::new_spanned(expr, "expected integer literal")),
271 }
272 }
273 _ => Err(syn::Error::new_spanned(attr, "expected #[attr(number)]")),
274 }
275}
276
277fn generate_survey_fn(input: &DeriveInput, type_attrs: &TypeAttrs) -> syn::Result<TokenStream2> {
282 let prelude = match &type_attrs.prelude {
283 Some(s) => quote! { Some(#s.to_string()) },
284 None => quote! { None },
285 };
286
287 let epilogue = match &type_attrs.epilogue {
288 Some(s) => quote! { Some(#s.to_string()) },
289 None => quote! { None },
290 };
291
292 let questions = match &input.data {
293 Data::Struct(data) => generate_struct_questions(data, type_attrs.validate_fields.as_ref())?,
294 Data::Enum(data) => generate_enum_questions(data, &input.ident)?,
295 Data::Union(_) => {
296 return Err(syn::Error::new_spanned(
297 input,
298 "Survey cannot be derived for unions",
299 ));
300 }
301 };
302
303 Ok(quote! {
304 elicitor::SurveyDefinition {
305 prelude: #prelude,
306 questions: #questions,
307 epilogue: #epilogue,
308 }
309 })
310}
311
312fn generate_struct_questions(
313 data: &syn::DataStruct,
314 propagated_validator: Option<&Ident>,
315) -> syn::Result<TokenStream2> {
316 let mut questions = Vec::new();
317
318 match &data.fields {
319 Fields::Named(fields) => {
320 for field in &fields.named {
321 let field_name = field.ident.as_ref().unwrap();
322 let field_name_str = field_name.to_string();
323 let attrs = FieldAttrs::extract(&field.attrs)?;
324 let question = generate_question_for_field(
325 &field_name_str,
326 &field.ty,
327 &attrs,
328 propagated_validator,
329 )?;
330 questions.push(question);
331 }
332 }
333 Fields::Unnamed(fields) => {
334 for (i, field) in fields.unnamed.iter().enumerate() {
335 let field_name_str = i.to_string();
336 let attrs = FieldAttrs::extract(&field.attrs)?;
337 let question = generate_question_for_field(
338 &field_name_str,
339 &field.ty,
340 &attrs,
341 propagated_validator,
342 )?;
343 questions.push(question);
344 }
345 }
346 Fields::Unit => {}
347 }
348
349 Ok(quote! { vec![#(#questions),*] })
350}
351
352fn generate_enum_questions(data: &syn::DataEnum, _enum_name: &Ident) -> syn::Result<TokenStream2> {
353 let mut variants = Vec::new();
355
356 for variant in &data.variants {
357 let variant_name = variant.ident.to_string();
358
359 let variant_attrs = FieldAttrs::extract(&variant.attrs)?;
361 let display_name = variant_attrs.ask.unwrap_or_else(|| variant_name.clone());
362
363 let kind = match &variant.fields {
364 Fields::Unit => quote! { elicitor::QuestionKind::Unit },
365 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
366 let field = &fields.unnamed[0];
368 let attrs = FieldAttrs::extract(&field.attrs)?;
369 let q = generate_question_for_field("0", &field.ty, &attrs, None)?;
370 quote! { elicitor::QuestionKind::AllOf(elicitor::AllOfQuestion::new(vec![#q])) }
371 }
372 Fields::Unnamed(fields) => {
373 let mut qs = Vec::new();
375 for (i, field) in fields.unnamed.iter().enumerate() {
376 let name = i.to_string();
377 let attrs = FieldAttrs::extract(&field.attrs)?;
378 let q = generate_question_for_field(&name, &field.ty, &attrs, None)?;
379 qs.push(q);
380 }
381 quote! { elicitor::QuestionKind::AllOf(elicitor::AllOfQuestion::new(vec![#(#qs),*])) }
382 }
383 Fields::Named(fields) => {
384 let mut qs = Vec::new();
386 for field in &fields.named {
387 let name = field.ident.as_ref().unwrap().to_string();
388 let attrs = FieldAttrs::extract(&field.attrs)?;
389 let q = generate_question_for_field(&name, &field.ty, &attrs, None)?;
390 qs.push(q);
391 }
392 quote! { elicitor::QuestionKind::AllOf(elicitor::AllOfQuestion::new(vec![#(#qs),*])) }
393 }
394 };
395
396 variants.push(quote! {
397 elicitor::Variant {
398 name: #display_name.to_string(),
399 kind: #kind,
400 }
401 });
402 }
403
404 Ok(quote! {
406 vec![elicitor::Question::new(
407 elicitor::ResponsePath::empty(),
408 String::new(), elicitor::QuestionKind::OneOf(elicitor::OneOfQuestion {
410 variants: vec![#(#variants),*],
411 default: None,
412 }),
413 )]
414 })
415}
416
417fn generate_question_for_field(
418 field_name: &str,
419 ty: &Type,
420 attrs: &FieldAttrs,
421 propagated_validator: Option<&Ident>,
422) -> syn::Result<TokenStream2> {
423 let default_prompt = field_name
425 .split('_')
426 .map(|word| {
427 let mut chars = word.chars();
428 match chars.next() {
429 None => String::new(),
430 Some(first) => first.to_uppercase().chain(chars).collect(),
431 }
432 })
433 .collect::<Vec<_>>()
434 .join(" ");
435 let ask = attrs.ask.clone().unwrap_or(default_prompt);
436 let kind = generate_question_kind(ty, attrs, propagated_validator)?;
437
438 Ok(quote! {
439 elicitor::Question::new(
440 elicitor::ResponsePath::new(#field_name),
441 #ask.to_string(),
442 #kind,
443 )
444 })
445}
446
447fn generate_question_kind(
448 ty: &Type,
449 attrs: &FieldAttrs,
450 propagated_validator: Option<&Ident>,
451) -> syn::Result<TokenStream2> {
452 if attrs.mask {
454 let validate_opt = match (&attrs.validate, propagated_validator) {
455 (Some(v), _) => {
456 let v_str = v.to_string();
457 quote! { Some(#v_str.to_string()) }
458 }
459 (None, Some(v)) => {
460 let v_str = v.to_string();
461 quote! { Some(#v_str.to_string()) }
462 }
463 (None, None) => quote! { None },
464 };
465 return Ok(quote! {
466 elicitor::QuestionKind::Masked(elicitor::MaskedQuestion::with_validator(#validate_opt))
467 });
468 }
469
470 if attrs.multiline {
471 let validate_opt = match (&attrs.validate, propagated_validator) {
472 (Some(v), _) => {
473 let v_str = v.to_string();
474 quote! { Some(#v_str.to_string()) }
475 }
476 (None, Some(v)) => {
477 let v_str = v.to_string();
478 quote! { Some(#v_str.to_string()) }
479 }
480 (None, None) => quote! { None },
481 };
482 return Ok(quote! {
483 elicitor::QuestionKind::Multiline(elicitor::MultilineQuestion::with_validator(#validate_opt))
484 });
485 }
486
487 if let Some(inner_ty) = extract_vec_inner_type(ty) {
489 if attrs.multiselect {
491 return Ok(quote! {
492 elicitor::QuestionKind::AnyOf(elicitor::AnyOfQuestion {
493 variants: <#inner_ty as elicitor::Survey>::survey()
494 .questions
495 .into_iter()
496 .flat_map(|q| match q.kind() {
497 elicitor::QuestionKind::OneOf(one_of) => one_of.variants.clone(),
498 _ => vec![],
499 })
500 .collect(),
501 defaults: vec![],
502 })
503 });
504 }
505
506 let inner_type_name = type_to_string(&inner_ty);
508 match inner_type_name.as_str() {
509 "String" => {
510 return Ok(quote! {
511 elicitor::QuestionKind::List(elicitor::ListQuestion::strings())
512 });
513 }
514 "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => {
515 let min_opt = match attrs.min {
516 Some(m) => quote! { Some(#m) },
517 None => quote! { None },
518 };
519 let max_opt = match attrs.max {
520 Some(m) => quote! { Some(#m) },
521 None => quote! { None },
522 };
523 return Ok(quote! {
524 elicitor::QuestionKind::List(elicitor::ListQuestion::ints_with_bounds(#min_opt, #max_opt))
525 });
526 }
527 "f32" | "f64" => {
528 let min_opt = match attrs.min {
529 Some(m) => {
530 let f = m as f64;
531 quote! { Some(#f) }
532 }
533 None => quote! { None },
534 };
535 let max_opt = match attrs.max {
536 Some(m) => {
537 let f = m as f64;
538 quote! { Some(#f) }
539 }
540 None => quote! { None },
541 };
542 return Ok(quote! {
543 elicitor::QuestionKind::List(elicitor::ListQuestion::floats_with_bounds(#min_opt, #max_opt))
544 });
545 }
546 _ => {
547 }
549 }
550 }
551
552 let type_name = type_to_string(ty);
554
555 match type_name.as_str() {
556 "String" | "&str" => {
557 let validate_opt = match (&attrs.validate, propagated_validator) {
558 (Some(v), _) => {
559 let v_str = v.to_string();
560 quote! { Some(#v_str.to_string()) }
561 }
562 (None, Some(v)) => {
563 let v_str = v.to_string();
564 quote! { Some(#v_str.to_string()) }
565 }
566 (None, None) => quote! { None },
567 };
568 Ok(quote! {
569 elicitor::QuestionKind::Input(elicitor::InputQuestion::with_validator(#validate_opt))
570 })
571 }
572 "bool" => Ok(quote! {
573 elicitor::QuestionKind::Confirm(elicitor::ConfirmQuestion::new())
574 }),
575 "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => {
576 let min_opt = match attrs.min {
577 Some(m) => quote! { Some(#m) },
578 None => quote! { None },
579 };
580 let max_opt = match attrs.max {
581 Some(m) => quote! { Some(#m) },
582 None => quote! { None },
583 };
584 let validate_opt = match (&attrs.validate, propagated_validator) {
586 (Some(v), _) => {
587 let v_str = v.to_string();
588 quote! { Some(#v_str.to_string()) }
589 }
590 (None, Some(v)) => {
591 let v_str = v.to_string();
592 quote! { Some(#v_str.to_string()) }
593 }
594 (None, None) => quote! { None },
595 };
596 Ok(quote! {
597 elicitor::QuestionKind::Int(elicitor::IntQuestion::with_bounds_and_validator(#min_opt, #max_opt, #validate_opt))
598 })
599 }
600 "f32" | "f64" => {
601 let min_opt = match attrs.min {
602 Some(m) => {
603 let f = m as f64;
604 quote! { Some(#f) }
605 }
606 None => quote! { None },
607 };
608 let max_opt = match attrs.max {
609 Some(m) => {
610 let f = m as f64;
611 quote! { Some(#f) }
612 }
613 None => quote! { None },
614 };
615 let validate_opt = match (&attrs.validate, propagated_validator) {
617 (Some(v), _) => {
618 let v_str = v.to_string();
619 quote! { Some(#v_str.to_string()) }
620 }
621 (None, Some(v)) => {
622 let v_str = v.to_string();
623 quote! { Some(#v_str.to_string()) }
624 }
625 (None, None) => quote! { None },
626 };
627 Ok(quote! {
628 elicitor::QuestionKind::Float(elicitor::FloatQuestion::with_bounds_and_validator(#min_opt, #max_opt, #validate_opt))
629 })
630 }
631 "PathBuf" => Ok(quote! {
632 elicitor::QuestionKind::Input(elicitor::InputQuestion::new())
633 }),
634 _ => {
635 if let Some(inner_ty) = extract_option_inner_type(ty) {
637 let inner_kind = generate_question_kind(&inner_ty, attrs, propagated_validator)?;
638 return Ok(inner_kind);
640 }
641
642 Ok(quote! {
644 elicitor::QuestionKind::AllOf(
645 elicitor::AllOfQuestion::new(<#ty as elicitor::Survey>::survey().questions)
646 )
647 })
648 }
649 }
650}
651
652fn type_to_string(ty: &Type) -> String {
653 match ty {
654 Type::Path(path) => path
655 .path
656 .segments
657 .last()
658 .map(|s| s.ident.to_string())
659 .unwrap_or_default(),
660 _ => String::new(),
661 }
662}
663
664fn extract_option_inner_type(ty: &Type) -> Option<Type> {
665 if let Type::Path(path) = ty
666 && let Some(segment) = path.path.segments.last()
667 && segment.ident == "Option"
668 && let syn::PathArguments::AngleBracketed(args) = &segment.arguments
669 && let Some(syn::GenericArgument::Type(inner)) = args.args.first()
670 {
671 return Some(inner.clone());
672 }
673 None
674}
675
676fn extract_vec_inner_type(ty: &Type) -> Option<Type> {
677 if let Type::Path(path) = ty
678 && let Some(segment) = path.path.segments.last()
679 && segment.ident == "Vec"
680 && let syn::PathArguments::AngleBracketed(args) = &segment.arguments
681 && let Some(syn::GenericArgument::Type(inner)) = args.args.first()
682 {
683 return Some(inner.clone());
684 }
685 None
686}
687
688fn generate_from_responses_fn(input: &DeriveInput) -> syn::Result<TokenStream2> {
693 match &input.data {
694 Data::Struct(data) => generate_from_responses_struct(&input.ident, data),
695 Data::Enum(data) => generate_from_responses_enum(&input.ident, data),
696 Data::Union(_) => Err(syn::Error::new_spanned(
697 input,
698 "Survey cannot be derived for unions",
699 )),
700 }
701}
702
703fn generate_from_responses_struct(
704 name: &Ident,
705 data: &syn::DataStruct,
706) -> syn::Result<TokenStream2> {
707 match &data.fields {
708 Fields::Named(fields) => {
709 let field_inits: Vec<_> = fields
710 .named
711 .iter()
712 .map(|f| {
713 let field_name = f.ident.as_ref().unwrap();
714 let field_name_str = field_name.to_string();
715 let ty = &f.ty;
716 let extraction = generate_value_extraction(&field_name_str, ty);
717 quote! { #field_name: #extraction }
718 })
719 .collect();
720
721 Ok(quote! {
722 #name {
723 #(#field_inits),*
724 }
725 })
726 }
727 Fields::Unnamed(fields) => {
728 let field_inits: Vec<_> = fields
729 .unnamed
730 .iter()
731 .enumerate()
732 .map(|(i, f)| {
733 let field_name_str = i.to_string();
734 let ty = &f.ty;
735 generate_value_extraction(&field_name_str, ty)
736 })
737 .collect();
738
739 Ok(quote! {
740 #name(#(#field_inits),*)
741 })
742 }
743 Fields::Unit => Ok(quote! { #name }),
744 }
745}
746
747fn generate_from_responses_enum(name: &Ident, data: &syn::DataEnum) -> syn::Result<TokenStream2> {
748 let variant_arms: Vec<_> = data
749 .variants
750 .iter()
751 .enumerate()
752 .map(|(idx, variant)| {
753 let variant_name = &variant.ident;
754
755 let construction = match &variant.fields {
756 Fields::Unit => quote! { #name::#variant_name },
757 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
758 let ty = &fields.unnamed[0].ty;
759 let extraction = generate_value_extraction("0", ty);
760 quote! { #name::#variant_name(#extraction) }
761 }
762 Fields::Unnamed(fields) => {
763 let extractions: Vec<_> = fields
764 .unnamed
765 .iter()
766 .enumerate()
767 .map(|(i, f)| {
768 let ty = &f.ty;
769 generate_value_extraction(&i.to_string(), ty)
770 })
771 .collect();
772 quote! { #name::#variant_name(#(#extractions),*) }
773 }
774 Fields::Named(fields) => {
775 let field_inits: Vec<_> = fields
776 .named
777 .iter()
778 .map(|f| {
779 let field_name = f.ident.as_ref().unwrap();
780 let field_name_str = field_name.to_string();
781 let ty = &f.ty;
782 let extraction = generate_value_extraction(&field_name_str, ty);
783 quote! { #field_name: #extraction }
784 })
785 .collect();
786 quote! { #name::#variant_name { #(#field_inits),* } }
787 }
788 };
789
790 quote! { #idx => #construction }
791 })
792 .collect();
793
794 Ok(quote! {
795 let variant_idx = responses
796 .get_chosen_variant(&elicitor::ResponsePath::new(elicitor::SELECTED_VARIANT_KEY))
797 .expect("missing selected_variant");
798 match variant_idx {
799 #(#variant_arms,)*
800 _ => panic!("invalid variant index"),
801 }
802 })
803}
804
805fn generate_value_extraction(field_name: &str, ty: &Type) -> TokenStream2 {
806 let type_name = type_to_string(ty);
807 let path_expr = quote! { elicitor::ResponsePath::new(#field_name) };
808
809 match type_name.as_str() {
810 "String" => quote! {
811 responses.get_string(&#path_expr).expect("missing string").to_string()
812 },
813 "bool" => quote! {
814 responses.get_bool(&#path_expr).expect("missing bool")
815 },
816 "i8" | "i16" | "i32" | "i64" | "isize" => quote! {
817 responses.get_int(&#path_expr).expect("missing int") as #ty
818 },
819 "u8" | "u16" | "u32" | "u64" | "usize" => quote! {
820 responses.get_int(&#path_expr).expect("missing int") as #ty
821 },
822 "f32" | "f64" => quote! {
823 responses.get_float(&#path_expr).expect("missing float") as #ty
824 },
825 "PathBuf" => quote! {
826 std::path::PathBuf::from(responses.get_string(&#path_expr).expect("missing path"))
827 },
828 _ => {
829 if let Some(inner_ty) = extract_option_inner_type(ty) {
831 let inner_extraction = generate_value_extraction(field_name, &inner_ty);
832 return quote! {
833 if responses.has_value(&#path_expr) {
834 Some(#inner_extraction)
835 } else {
836 None
837 }
838 };
839 }
840
841 if let Some(inner_ty) = extract_vec_inner_type(ty) {
843 let inner_type_name = type_to_string(&inner_ty);
844
845 match inner_type_name.as_str() {
847 "String" => {
848 return quote! {
849 responses.get_string_list(&#path_expr)
850 .expect("missing string list")
851 .to_vec()
852 };
853 }
854 "i8" | "i16" | "i32" | "i64" | "isize" => {
855 return quote! {
856 responses.get_int_list(&#path_expr)
857 .expect("missing int list")
858 .iter()
859 .map(|&n| n as #inner_ty)
860 .collect()
861 };
862 }
863 "u8" | "u16" | "u32" | "u64" | "usize" => {
864 return quote! {
865 responses.get_int_list(&#path_expr)
866 .expect("missing int list")
867 .iter()
868 .map(|&n| n as #inner_ty)
869 .collect()
870 };
871 }
872 "f32" | "f64" => {
873 return quote! {
874 responses.get_float_list(&#path_expr)
875 .expect("missing float list")
876 .iter()
877 .map(|&n| n as #inner_ty)
878 .collect()
879 };
880 }
881 _ => {
882 let variants_path = quote! {
884 elicitor::ResponsePath::new(
885 &format!("{}.{}", #field_name, elicitor::SELECTED_VARIANTS_KEY)
886 )
887 };
888 return quote! {
889 {
890 let indices = responses
891 .get_chosen_variants(&#variants_path)
892 .map(|s| s.to_vec())
893 .unwrap_or_default();
894
895 indices
897 .iter()
898 .enumerate()
899 .map(|(item_idx, _variant_idx)| {
900 let item_prefix = elicitor::ResponsePath::new(
901 &format!("{}.{}", #field_name, item_idx)
902 );
903 let item_responses = responses.filter_prefix(&item_prefix);
904 <#inner_ty as elicitor::Survey>::from_responses(&item_responses)
905 })
906 .collect()
907 }
908 };
909 }
910 }
911 }
912
913 quote! {
915 {
916 let prefix = elicitor::ResponsePath::new(#field_name);
917 let nested_responses = responses.filter_prefix(&prefix);
918 <#ty as elicitor::Survey>::from_responses(&nested_responses)
919 }
920 }
921 }
922 }
923}
924
925fn generate_field_accessors(input: &DeriveInput) -> syn::Result<TokenStream2> {
932 let mut accessors = Vec::new();
933
934 match &input.data {
935 Data::Struct(data) => {
936 if let Fields::Named(fields) = &data.fields {
937 for field in &fields.named {
938 let field_name = field.ident.as_ref().unwrap();
939 let field_name_str = field_name.to_string();
940 let ty = &field.ty;
941
942 let accessor = generate_field_accessor_method(&field_name_str, field_name, ty);
943 accessors.push(accessor);
944 }
945 }
946 }
947 Data::Enum(_) => {
948 }
950 Data::Union(_) => {}
951 }
952
953 Ok(quote! {
954 #(#accessors)*
955 })
956}
957
958fn generate_field_accessor_method(
960 field_name_str: &str,
961 field_name: &Ident,
962 ty: &Type,
963) -> TokenStream2 {
964 let method_name = format_ident!("get_{}", field_name);
965 let type_name = type_to_string(ty);
966 let path_expr = quote! { elicitor::ResponsePath::new(#field_name_str) };
967
968 match type_name.as_str() {
969 "String" => quote! {
970 pub fn #method_name(responses: &elicitor::Responses) -> Option<String> {
972 responses.get_string(&#path_expr).ok().map(|s| s.to_string())
973 }
974 },
975 "bool" => quote! {
976 pub fn #method_name(responses: &elicitor::Responses) -> Option<bool> {
978 responses.get_bool(&#path_expr).ok()
979 }
980 },
981 "i8" | "i16" | "i32" | "i64" | "isize" => quote! {
982 pub fn #method_name(responses: &elicitor::Responses) -> Option<#ty> {
984 responses.get_int(&#path_expr).ok().map(|n| n as #ty)
985 }
986 },
987 "u8" | "u16" | "u32" | "u64" | "usize" => quote! {
988 pub fn #method_name(responses: &elicitor::Responses) -> Option<#ty> {
990 responses.get_int(&#path_expr).ok().map(|n| n as #ty)
991 }
992 },
993 "f32" | "f64" => quote! {
994 pub fn #method_name(responses: &elicitor::Responses) -> Option<#ty> {
996 responses.get_float(&#path_expr).ok().map(|n| n as #ty)
997 }
998 },
999 "PathBuf" => quote! {
1000 pub fn #method_name(responses: &elicitor::Responses) -> Option<std::path::PathBuf> {
1002 responses.get_string(&#path_expr).ok().map(std::path::PathBuf::from)
1003 }
1004 },
1005 _ => {
1006 quote! {}
1009 }
1010 }
1011}
1012
1013fn generate_validation_context(input: &DeriveInput) -> syn::Result<TokenStream2> {
1020 let name = &input.ident;
1021 let context_name = format_ident!("{}ValidationContext", name);
1022
1023 let mut accessors = Vec::new();
1024
1025 match &input.data {
1026 Data::Struct(data) => {
1027 if let Fields::Named(fields) = &data.fields {
1028 for field in &fields.named {
1029 let field_name = field.ident.as_ref().unwrap();
1030 let field_name_str = field_name.to_string();
1031 let ty = &field.ty;
1032
1033 let accessor =
1034 generate_context_accessor_method(&field_name_str, field_name, ty);
1035 accessors.push(accessor);
1036 }
1037 }
1038 }
1039 Data::Enum(_) => {
1040 }
1042 Data::Union(_) => {}
1043 }
1044
1045 Ok(quote! {
1046 pub struct #context_name<'a> {
1048 responses: &'a elicitor::Responses,
1049 prefix: elicitor::ResponsePath,
1050 }
1051
1052 impl<'a> #context_name<'a> {
1053 pub fn new(responses: &'a elicitor::Responses, prefix: elicitor::ResponsePath) -> Self {
1055 Self { responses, prefix }
1056 }
1057
1058 pub fn prefix(&self) -> &elicitor::ResponsePath {
1060 &self.prefix
1061 }
1062
1063 pub fn responses(&self) -> &elicitor::Responses {
1065 self.responses
1066 }
1067
1068 #(#accessors)*
1069 }
1070 })
1071}
1072
1073fn generate_context_accessor_method(
1075 field_name_str: &str,
1076 field_name: &Ident,
1077 ty: &Type,
1078) -> TokenStream2 {
1079 let method_name = format_ident!("get_{}", field_name);
1080 let type_name = type_to_string(ty);
1081
1082 match type_name.as_str() {
1083 "String" => quote! {
1084 pub fn #method_name(&self) -> Option<String> {
1086 let path = self.prefix.child(#field_name_str);
1087 self.responses.get_string(&path).ok().map(|s| s.to_string())
1088 }
1089 },
1090 "bool" => quote! {
1091 pub fn #method_name(&self) -> Option<bool> {
1093 let path = self.prefix.child(#field_name_str);
1094 self.responses.get_bool(&path).ok()
1095 }
1096 },
1097 "i8" | "i16" | "i32" | "i64" | "isize" => quote! {
1098 pub fn #method_name(&self) -> Option<#ty> {
1100 let path = self.prefix.child(#field_name_str);
1101 self.responses.get_int(&path).ok().map(|n| n as #ty)
1102 }
1103 },
1104 "u8" | "u16" | "u32" | "u64" | "usize" => quote! {
1105 pub fn #method_name(&self) -> Option<#ty> {
1107 let path = self.prefix.child(#field_name_str);
1108 self.responses.get_int(&path).ok().map(|n| n as #ty)
1109 }
1110 },
1111 "f32" | "f64" => quote! {
1112 pub fn #method_name(&self) -> Option<#ty> {
1114 let path = self.prefix.child(#field_name_str);
1115 self.responses.get_float(&path).ok().map(|n| n as #ty)
1116 }
1117 },
1118 "PathBuf" => quote! {
1119 pub fn #method_name(&self) -> Option<std::path::PathBuf> {
1121 let path = self.prefix.child(#field_name_str);
1122 self.responses.get_string(&path).ok().map(std::path::PathBuf::from)
1123 }
1124 },
1125 _ => {
1126 quote! {}
1128 }
1129 }
1130}
1131
1132fn generate_validate_field_fn(input: &DeriveInput) -> syn::Result<TokenStream2> {
1137 let mut validators = Vec::new();
1138
1139 let path_matches_field = |field_name: &str| -> TokenStream2 {
1147 quote! {
1148 (path.as_str() == #field_name || path.as_str().ends_with(&format!(".{}", #field_name)))
1149 }
1150 };
1151
1152 let field_names: Vec<String> = match &input.data {
1154 Data::Struct(data) => {
1155 if let Fields::Named(fields) = &data.fields {
1156 fields
1157 .named
1158 .iter()
1159 .filter_map(|f| f.ident.as_ref().map(|i| i.to_string()))
1160 .collect()
1161 } else {
1162 vec![]
1163 }
1164 }
1165 Data::Enum(_) => vec![],
1166 _ => vec![],
1167 };
1168
1169 let type_attrs = TypeAttrs::extract(&input.attrs)?;
1172 if let Some(validator) = &type_attrs.validate_fields {
1173 if !field_names.is_empty() {
1174 let field_checks: Vec<TokenStream2> = field_names
1176 .iter()
1177 .map(|name| path_matches_field(name))
1178 .collect();
1179
1180 validators.push(quote! {
1181 if #(#field_checks)||* {
1183 if let Err(e) = #validator(value, responses, path) {
1184 return Err(e);
1185 }
1186 }
1187 });
1188 } else {
1189 validators.push(quote! {
1191 if let Err(e) = #validator(value, responses, path) {
1192 return Err(e);
1193 }
1194 });
1195 }
1196 }
1197
1198 match &input.data {
1199 Data::Struct(data) => {
1200 if let Fields::Named(fields) = &data.fields {
1201 for field in &fields.named {
1202 let attrs = FieldAttrs::extract(&field.attrs)?;
1203 let ty = &field.ty;
1204 let field_name = field.ident.as_ref().unwrap().to_string();
1205
1206 if let Some(validator) = &attrs.validate {
1207 let path_check = path_matches_field(&field_name);
1208 validators.push(quote! {
1210 if #path_check {
1211 if let Err(e) = #validator(value, responses, path) {
1212 return Err(e);
1213 }
1214 }
1215 });
1216 }
1217
1218 let type_name = type_to_string(ty);
1220 let is_primitive = matches!(
1221 type_name.as_str(),
1222 "String"
1223 | "&str"
1224 | "bool"
1225 | "i8"
1226 | "i16"
1227 | "i32"
1228 | "i64"
1229 | "isize"
1230 | "u8"
1231 | "u16"
1232 | "u32"
1233 | "u64"
1234 | "usize"
1235 | "f32"
1236 | "f64"
1237 | "PathBuf"
1238 );
1239
1240 if !is_primitive
1242 && extract_vec_inner_type(ty).is_none()
1243 && extract_option_inner_type(ty).is_none()
1244 {
1245 validators.push(quote! {
1246 if let Err(e) = <#ty as elicitor::Survey>::validate_field(value, responses, path) {
1248 return Err(e);
1249 }
1250 });
1251 }
1252 }
1253 }
1254 }
1255 Data::Enum(data) => {
1256 for variant in &data.variants {
1257 match &variant.fields {
1258 Fields::Named(fields) => {
1259 for field in &fields.named {
1260 let attrs = FieldAttrs::extract(&field.attrs)?;
1261 let field_name = field.ident.as_ref().unwrap().to_string();
1262
1263 if let Some(validator) = &attrs.validate {
1264 let path_check = path_matches_field(&field_name);
1265 validators.push(quote! {
1266 if #path_check {
1267 if let Err(e) = #validator(value, responses, path) {
1268 return Err(e);
1269 }
1270 }
1271 });
1272 }
1273 }
1274 }
1275 Fields::Unnamed(fields) => {
1276 let variant_name = variant.ident.to_string();
1278 for (idx, field) in fields.unnamed.iter().enumerate() {
1279 let attrs = FieldAttrs::extract(&field.attrs)?;
1280 let field_name = if fields.unnamed.len() == 1 {
1282 variant_name.clone()
1283 } else {
1284 format!("{}.{}", variant_name, idx)
1285 };
1286
1287 if let Some(validator) = &attrs.validate {
1288 let path_check = path_matches_field(&field_name);
1289 validators.push(quote! {
1290 if #path_check {
1291 if let Err(e) = #validator(value, responses, path) {
1292 return Err(e);
1293 }
1294 }
1295 });
1296 }
1297 }
1298 }
1299 Fields::Unit => {}
1300 }
1301 }
1302 }
1303 Data::Union(_) => {}
1304 }
1305
1306 Ok(quote! {
1307 #(#validators)*
1308 Ok(())
1309 })
1310}
1311
1312fn generate_validate_all_fn(
1313 _input: &DeriveInput,
1314 type_attrs: &TypeAttrs,
1315) -> syn::Result<TokenStream2> {
1316 let composite_call = if let Some(validator) = &type_attrs.validate {
1317 quote! { #validator(responses) }
1318 } else {
1319 quote! { std::collections::HashMap::new() }
1320 };
1321
1322 Ok(quote! {
1325 #composite_call
1326 })
1327}
1328
1329fn generate_validator_checks(input: &DeriveInput) -> syn::Result<TokenStream2> {
1330 let mut checks = Vec::new();
1331
1332 let type_attrs = TypeAttrs::extract(&input.attrs)?;
1334 if let Some(validator) = &type_attrs.validate {
1335 checks.push(quote! {
1336 const _: fn(&elicitor::Responses) -> std::collections::HashMap<elicitor::ResponsePath, String> = #validator;
1337 });
1338 }
1339
1340 if let Some(validator) = &type_attrs.validate_fields {
1342 checks.push(quote! {
1343 const _: fn(&elicitor::ResponseValue, &elicitor::Responses, &elicitor::ResponsePath) -> Result<(), String> = #validator;
1344 });
1345 }
1346
1347 let check_field = |field: &syn::Field, checks: &mut Vec<TokenStream2>| -> syn::Result<()> {
1349 let attrs = FieldAttrs::extract(&field.attrs)?;
1350 if let Some(validator) = &attrs.validate {
1351 checks.push(quote! {
1352 const _: fn(&elicitor::ResponseValue, &elicitor::Responses, &elicitor::ResponsePath) -> Result<(), String> = #validator;
1353 });
1354 }
1355 Ok(())
1356 };
1357
1358 match &input.data {
1359 Data::Struct(data) => {
1360 if let Fields::Named(fields) = &data.fields {
1361 for field in &fields.named {
1362 check_field(field, &mut checks)?;
1363 }
1364 }
1365 }
1366 Data::Enum(data) => {
1367 for variant in &data.variants {
1368 match &variant.fields {
1369 Fields::Named(fields) => {
1370 for field in &fields.named {
1371 check_field(field, &mut checks)?;
1372 }
1373 }
1374 Fields::Unnamed(fields) => {
1375 for field in &fields.unnamed {
1376 check_field(field, &mut checks)?;
1377 }
1378 }
1379 Fields::Unit => {}
1380 }
1381 }
1382 }
1383 Data::Union(_) => {}
1384 }
1385
1386 Ok(quote! { #(#checks)* })
1387}
1388
1389fn generate_builder(input: &DeriveInput, builder_name: &Ident) -> syn::Result<TokenStream2> {
1394 let name = &input.ident;
1395
1396 let suggest_builder = generate_suggest_builder(input)?;
1398
1399 let option_builders = collect_option_builders(input);
1401
1402 let mut suggest_methods = Vec::new();
1404 let mut assume_methods = Vec::new();
1405
1406 generate_builder_methods_for_type(
1407 input,
1408 "", &mut suggest_methods,
1410 &mut assume_methods,
1411 )?;
1412
1413 let with_suggestions_body = generate_with_suggestions_body(input);
1415
1416 Ok(quote! {
1417 pub struct #builder_name {
1419 suggestions: std::collections::HashMap<String, elicitor::ResponseValue>,
1420 assumptions: std::collections::HashMap<String, elicitor::ResponseValue>,
1421 }
1422
1423 impl #builder_name {
1424 pub fn new() -> Self {
1426 Self {
1427 suggestions: std::collections::HashMap::new(),
1428 assumptions: std::collections::HashMap::new(),
1429 }
1430 }
1431
1432 pub fn with_suggestions(mut self, instance: &#name) -> Self {
1434 #with_suggestions_body
1435 self
1436 }
1437
1438 #(#suggest_methods)*
1439 #(#assume_methods)*
1440
1441 pub fn run<B: elicitor::SurveyBackend>(
1443 self,
1444 backend: B,
1445 ) -> Result<#name, anyhow::Error> {
1446 let mut definition = #name::survey();
1447
1448 self.apply_to_definition(&mut definition);
1450
1451 let responses = backend.collect(
1453 &definition,
1454 &|value, responses, path| #name::validate_field(value, responses, path),
1455 ).map_err(Into::into)?;
1456
1457 Ok(#name::from_responses(&responses))
1459 }
1460
1461 fn apply_to_definition(&self, definition: &mut elicitor::SurveyDefinition) {
1462 for question in &mut definition.questions {
1463 self.apply_to_question(question, "");
1464 }
1465 }
1466
1467 fn apply_to_question(&self, question: &mut elicitor::Question, parent_prefix: &str) {
1468 let path_str = if parent_prefix.is_empty() {
1469 question.path().as_str().to_string()
1470 } else if question.path().as_str().is_empty() {
1471 parent_prefix.to_string()
1472 } else {
1473 format!("{}.{}", parent_prefix, question.path().as_str())
1474 };
1475
1476 let none_key = format!("{}.is_none", path_str);
1478 if let Some(elicitor::ResponseValue::Bool(true)) = self.assumptions.get(&none_key) {
1479 question.set_assumption(elicitor::ResponseValue::Bool(false));
1481 return;
1482 }
1483 if let Some(elicitor::ResponseValue::Bool(true)) = self.suggestions.get(&none_key) {
1484 question.set_suggestion(elicitor::ResponseValue::Bool(false));
1486 }
1487
1488 if let Some(value) = self.assumptions.get(&path_str) {
1490 question.set_assumption(value.clone());
1491 return;
1492 }
1493
1494 if let Some(value) = self.suggestions.get(&path_str) {
1496 question.set_suggestion(value.clone());
1497 }
1498
1499 match question.kind_mut() {
1501 elicitor::QuestionKind::AllOf(all_of) => {
1502 for q in all_of.questions_mut() {
1503 self.apply_to_question(q, &path_str);
1504 }
1505 }
1506 elicitor::QuestionKind::OneOf(one_of) => {
1507 let variant_key = format!("{}.selected_variant", path_str);
1509 if let Some(elicitor::ResponseValue::ChosenVariant(idx)) = self.assumptions.get(&variant_key) {
1510 one_of.default = Some(*idx);
1511 } else if let Some(elicitor::ResponseValue::ChosenVariant(idx)) = self.suggestions.get(&variant_key) {
1512 one_of.default = Some(*idx);
1513 }
1514
1515 for variant in &mut one_of.variants {
1517 match &mut variant.kind {
1518 elicitor::QuestionKind::AllOf(all_of) => {
1519 for q in all_of.questions_mut() {
1520 self.apply_to_question(q, &path_str);
1521 }
1522 }
1523 elicitor::QuestionKind::Unit => {}
1524 other => {
1525 let mut temp_q = elicitor::Question::new(
1527 elicitor::ResponsePath::new("0"),
1528 "",
1529 std::mem::replace(other, elicitor::QuestionKind::Unit),
1530 );
1531 self.apply_to_question(&mut temp_q, &path_str);
1532 *other = std::mem::replace(temp_q.kind_mut(), elicitor::QuestionKind::Unit);
1533 }
1534 }
1535 }
1536 }
1537 elicitor::QuestionKind::AnyOf(any_of) => {
1538 let variants_key = format!("{}.selected_variants", path_str);
1540 if let Some(elicitor::ResponseValue::ChosenVariants(indices)) = self.assumptions.get(&variants_key) {
1541 any_of.defaults = indices.clone();
1542 } else if let Some(elicitor::ResponseValue::ChosenVariants(indices)) = self.suggestions.get(&variants_key) {
1543 any_of.defaults = indices.clone();
1544 }
1545
1546 for variant in &mut any_of.variants {
1547 if let elicitor::QuestionKind::AllOf(all_of) = &mut variant.kind {
1548 for q in all_of.questions_mut() {
1549 self.apply_to_question(q, &path_str);
1550 }
1551 }
1552 }
1553 }
1554 _ => {}
1555 }
1556 }
1557 }
1558
1559 impl Default for #builder_name {
1560 fn default() -> Self {
1561 Self::new()
1562 }
1563 }
1564
1565 #suggest_builder
1566
1567 #(#option_builders)*
1568 })
1569}
1570
1571fn generate_suggest_builder(input: &DeriveInput) -> syn::Result<TokenStream2> {
1574 let name = &input.ident;
1575 let suggest_builder_name = format_ident!("{}SuggestBuilder", name);
1576
1577 match &input.data {
1578 Data::Struct(data) => {
1579 generate_suggest_builder_for_struct(name, &suggest_builder_name, data)
1580 }
1581 Data::Enum(data) => generate_suggest_builder_for_enum(name, &suggest_builder_name, data),
1582 Data::Union(_) => Ok(quote! {}),
1583 }
1584}
1585
1586fn generate_suggest_builder_for_struct(
1588 _name: &Ident,
1589 suggest_builder_name: &Ident,
1590 data: &syn::DataStruct,
1591) -> syn::Result<TokenStream2> {
1592 let mut field_methods = Vec::new();
1593
1594 if let Fields::Named(fields) = &data.fields {
1595 for field in &fields.named {
1596 let field_name = field.ident.as_ref().unwrap();
1597 let field_name_str = field_name.to_string();
1598 let ty = &field.ty;
1599
1600 let method = generate_suggest_builder_field_method(&field_name_str, ty)?;
1601 field_methods.push(method);
1602 }
1603 } else if let Fields::Unnamed(fields) = &data.fields {
1604 for (i, field) in fields.unnamed.iter().enumerate() {
1605 let field_name_str = i.to_string();
1606 let ty = &field.ty;
1607
1608 let method = generate_suggest_builder_field_method(&field_name_str, ty)?;
1609 field_methods.push(method);
1610 }
1611 }
1612
1613 Ok(quote! {
1614 pub struct #suggest_builder_name<'a> {
1616 map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1617 prefix: String,
1618 }
1619
1620 impl<'a> #suggest_builder_name<'a> {
1621 fn new(
1622 map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1623 prefix: String,
1624 ) -> Self {
1625 Self { map, prefix }
1626 }
1627
1628 fn path(&self, field: &str) -> String {
1629 if self.prefix.is_empty() {
1630 field.to_string()
1631 } else {
1632 format!("{}.{}", self.prefix, field)
1633 }
1634 }
1635
1636 #(#field_methods)*
1637 }
1638 })
1639}
1640
1641fn generate_suggest_builder_for_enum(
1643 name: &Ident,
1644 suggest_builder_name: &Ident,
1645 data: &syn::DataEnum,
1646) -> syn::Result<TokenStream2> {
1647 let mut select_methods = Vec::new();
1648 let mut variant_methods = Vec::new();
1649 let mut variant_builders = Vec::new();
1650
1651 for (idx, variant) in data.variants.iter().enumerate() {
1652 let variant_name = &variant.ident;
1653 let variant_snake = to_snake_case(&variant_name.to_string());
1654 let select_method_name = format_ident!("suggest_{}", variant_snake);
1655
1656 select_methods.push(quote! {
1658 pub fn #select_method_name(self) -> Self {
1660 self.map.insert(
1661 format!("{}.selected_variant", self.prefix),
1662 elicitor::ResponseValue::ChosenVariant(#idx),
1663 );
1664 self
1665 }
1666 });
1667
1668 match &variant.fields {
1670 Fields::Named(fields) if !fields.named.is_empty() => {
1671 let variant_builder_name = format_ident!("{}{}SuggestBuilder", name, variant_name);
1672 let method_name = format_ident!("{}", variant_snake);
1673
1674 variant_methods.push(quote! {
1675 pub fn #method_name<F>(self, f: F) -> Self
1677 where
1678 F: FnOnce(#variant_builder_name<'_>) -> #variant_builder_name<'_>,
1679 {
1680 let builder = #variant_builder_name::new(self.map, self.prefix.clone());
1681 f(builder);
1682 self
1683 }
1684 });
1685
1686 let mut field_methods = Vec::new();
1688 for field in &fields.named {
1689 let field_name = field.ident.as_ref().unwrap();
1690 let field_name_str = field_name.to_string();
1691 let ty = &field.ty;
1692
1693 let method = generate_suggest_builder_field_method(&field_name_str, ty)?;
1694 field_methods.push(method);
1695 }
1696
1697 variant_builders.push(quote! {
1698 pub struct #variant_builder_name<'a> {
1700 map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1701 prefix: String,
1702 }
1703
1704 impl<'a> #variant_builder_name<'a> {
1705 fn new(
1706 map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1707 prefix: String,
1708 ) -> Self {
1709 Self { map, prefix }
1710 }
1711
1712 fn path(&self, field: &str) -> String {
1713 if self.prefix.is_empty() {
1714 field.to_string()
1715 } else {
1716 format!("{}.{}", self.prefix, field)
1717 }
1718 }
1719
1720 #(#field_methods)*
1721 }
1722 });
1723 }
1724 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
1725 let field = &fields.unnamed[0];
1727 let ty = &field.ty;
1728 let type_name = type_to_string(ty);
1729 let method_name = format_ident!("{}", variant_snake);
1730
1731 let is_primitive = matches!(
1733 type_name.as_str(),
1734 "String"
1735 | "bool"
1736 | "i8"
1737 | "i16"
1738 | "i32"
1739 | "i64"
1740 | "isize"
1741 | "u8"
1742 | "u16"
1743 | "u32"
1744 | "u64"
1745 | "usize"
1746 | "f32"
1747 | "f64"
1748 | "PathBuf"
1749 );
1750
1751 if is_primitive {
1752 let (param_type, conversion) = match type_name.as_str() {
1754 "String" => (
1755 quote! { impl Into<String> },
1756 quote! { elicitor::ResponseValue::String(value.into()) },
1757 ),
1758 "bool" => (
1759 quote! { bool },
1760 quote! { elicitor::ResponseValue::Bool(value) },
1761 ),
1762 "i8" | "i16" | "i32" | "i64" | "isize" => (
1763 quote! { #ty },
1764 quote! { elicitor::ResponseValue::Int(value as i64) },
1765 ),
1766 "u8" | "u16" | "u32" | "u64" | "usize" => (
1767 quote! { #ty },
1768 quote! { elicitor::ResponseValue::Int(value as i64) },
1769 ),
1770 "f32" | "f64" => (
1771 quote! { #ty },
1772 quote! { elicitor::ResponseValue::Float(value as f64) },
1773 ),
1774 "PathBuf" => (
1775 quote! { impl Into<std::path::PathBuf> },
1776 quote! { elicitor::ResponseValue::String(value.into().to_string_lossy().into_owned()) },
1777 ),
1778 _ => unreachable!(),
1779 };
1780
1781 variant_methods.push(quote! {
1782 pub fn #method_name(self, value: #param_type) -> Self {
1784 self.map.insert(
1785 format!("{}.0", self.prefix),
1786 #conversion,
1787 );
1788 self
1789 }
1790 });
1791 } else {
1792 let inner_builder_name = format_ident!("{}SuggestBuilder", type_name);
1794
1795 variant_methods.push(quote! {
1796 pub fn #method_name<F>(self, f: F) -> Self
1798 where
1799 F: FnOnce(#inner_builder_name<'_>) -> #inner_builder_name<'_>,
1800 {
1801 let builder = #inner_builder_name::new(
1802 self.map,
1803 format!("{}.0", self.prefix),
1804 );
1805 f(builder);
1806 self
1807 }
1808 });
1809 }
1810 }
1811 Fields::Unnamed(fields) if fields.unnamed.len() > 1 => {
1812 let variant_builder_name = format_ident!("{}{}SuggestBuilder", name, variant_name);
1814 let method_name = format_ident!("{}", variant_snake);
1815
1816 variant_methods.push(quote! {
1817 pub fn #method_name<F>(self, f: F) -> Self
1819 where
1820 F: FnOnce(#variant_builder_name<'_>) -> #variant_builder_name<'_>,
1821 {
1822 let builder = #variant_builder_name::new(self.map, self.prefix.clone());
1823 f(builder);
1824 self
1825 }
1826 });
1827
1828 let mut field_methods = Vec::new();
1830 for (i, field) in fields.unnamed.iter().enumerate() {
1831 let field_name_str = i.to_string();
1832 let ty = &field.ty;
1833
1834 let method = generate_suggest_builder_field_method(&field_name_str, ty)?;
1835 field_methods.push(method);
1836 }
1837
1838 variant_builders.push(quote! {
1839 pub struct #variant_builder_name<'a> {
1841 map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1842 prefix: String,
1843 }
1844
1845 impl<'a> #variant_builder_name<'a> {
1846 fn new(
1847 map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1848 prefix: String,
1849 ) -> Self {
1850 Self { map, prefix }
1851 }
1852
1853 fn path(&self, field: &str) -> String {
1854 if self.prefix.is_empty() {
1855 field.to_string()
1856 } else {
1857 format!("{}.{}", self.prefix, field)
1858 }
1859 }
1860
1861 #(#field_methods)*
1862 }
1863 });
1864 }
1865 _ => {
1866 }
1868 }
1869 }
1870
1871 Ok(quote! {
1872 pub struct #suggest_builder_name<'a> {
1874 map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1875 prefix: String,
1876 }
1877
1878 impl<'a> #suggest_builder_name<'a> {
1879 fn new(
1880 map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1881 prefix: String,
1882 ) -> Self {
1883 Self { map, prefix }
1884 }
1885
1886 #(#select_methods)*
1887 #(#variant_methods)*
1888 }
1889
1890 #(#variant_builders)*
1891 })
1892}
1893
1894fn generate_suggest_builder_field_method(field_name: &str, ty: &Type) -> syn::Result<TokenStream2> {
1896 let method_name = if field_name
1898 .chars()
1899 .next()
1900 .map(|c| c.is_ascii_digit())
1901 .unwrap_or(false)
1902 {
1903 format_ident!("_{}", field_name)
1904 } else {
1905 format_ident!("{}", field_name)
1906 };
1907 let type_name = type_to_string(ty);
1908
1909 if let Some(inner_ty) = extract_option_inner_type(ty) {
1911 return generate_option_suggest_method(field_name, &inner_ty);
1912 }
1913
1914 if extract_vec_inner_type(ty).is_some() {
1916 return Ok(quote! {});
1917 }
1918
1919 let (param_type, conversion) = match type_name.as_str() {
1921 "String" => (
1922 Some(quote! { impl Into<String> }),
1923 Some(quote! { elicitor::ResponseValue::String(value.into()) }),
1924 ),
1925 "bool" => (
1926 Some(quote! { bool }),
1927 Some(quote! { elicitor::ResponseValue::Bool(value) }),
1928 ),
1929 "i8" | "i16" | "i32" | "i64" | "isize" => (
1930 Some(quote! { #ty }),
1931 Some(quote! { elicitor::ResponseValue::Int(value as i64) }),
1932 ),
1933 "u8" | "u16" | "u32" | "u64" | "usize" => (
1934 Some(quote! { #ty }),
1935 Some(quote! { elicitor::ResponseValue::Int(value as i64) }),
1936 ),
1937 "f32" | "f64" => (
1938 Some(quote! { #ty }),
1939 Some(quote! { elicitor::ResponseValue::Float(value as f64) }),
1940 ),
1941 "PathBuf" => (
1942 Some(quote! { impl Into<std::path::PathBuf> }),
1943 Some(
1944 quote! { elicitor::ResponseValue::String(value.into().to_string_lossy().into_owned()) },
1945 ),
1946 ),
1947 _ => (None, None), };
1949
1950 if let (Some(param_type), Some(conversion)) = (param_type, conversion) {
1951 Ok(quote! {
1953 pub fn #method_name(self, value: #param_type) -> Self {
1955 self.map.insert(self.path(#field_name), #conversion);
1956 self
1957 }
1958 })
1959 } else {
1960 let inner_builder_name = format_ident!("{}SuggestBuilder", type_name);
1962
1963 Ok(quote! {
1964 pub fn #method_name<F>(self, f: F) -> Self
1966 where
1967 F: FnOnce(#inner_builder_name<'_>) -> #inner_builder_name<'_>,
1968 {
1969 let builder = #inner_builder_name::new(self.map, self.path(#field_name));
1970 f(builder);
1971 self
1972 }
1973 })
1974 }
1975}
1976
1977fn generate_option_suggest_method(field_name: &str, inner_ty: &Type) -> syn::Result<TokenStream2> {
1979 let method_name = format_ident!("{}", field_name);
1980 let inner_type_name = type_to_string(inner_ty);
1981 let option_builder_name =
1982 format_ident!("Option{}SuggestBuilder", capitalize_first(&inner_type_name));
1983
1984 Ok(quote! {
1985 pub fn #method_name<F>(self, f: F) -> Self
1987 where
1988 F: FnOnce(#option_builder_name<'_>) -> #option_builder_name<'_>,
1989 {
1990 let builder = #option_builder_name::new(self.map, self.path(#field_name));
1991 f(builder);
1992 self
1993 }
1994 })
1995}
1996
1997fn generate_option_builder(inner_ty: &Type) -> TokenStream2 {
1999 let inner_type_name = type_to_string(inner_ty);
2000 let option_builder_name =
2001 format_ident!("Option{}SuggestBuilder", capitalize_first(&inner_type_name));
2002
2003 let is_primitive = matches!(
2005 inner_type_name.as_str(),
2006 "String"
2007 | "bool"
2008 | "i8"
2009 | "i16"
2010 | "i32"
2011 | "i64"
2012 | "isize"
2013 | "u8"
2014 | "u16"
2015 | "u32"
2016 | "u64"
2017 | "usize"
2018 | "f32"
2019 | "f64"
2020 | "PathBuf"
2021 );
2022
2023 if is_primitive {
2024 let (some_param, some_conversion) = match inner_type_name.as_str() {
2026 "String" => (
2027 quote! { impl Into<String> },
2028 quote! { elicitor::ResponseValue::String(value.into()) },
2029 ),
2030 "bool" => (
2031 quote! { bool },
2032 quote! { elicitor::ResponseValue::Bool(value) },
2033 ),
2034 "i8" | "i16" | "i32" | "i64" | "isize" => (
2035 quote! { #inner_ty },
2036 quote! { elicitor::ResponseValue::Int(value as i64) },
2037 ),
2038 "u8" | "u16" | "u32" | "u64" | "usize" => (
2039 quote! { #inner_ty },
2040 quote! { elicitor::ResponseValue::Int(value as i64) },
2041 ),
2042 "f32" | "f64" => (
2043 quote! { #inner_ty },
2044 quote! { elicitor::ResponseValue::Float(value as f64) },
2045 ),
2046 "PathBuf" => (
2047 quote! { impl Into<std::path::PathBuf> },
2048 quote! { elicitor::ResponseValue::String(value.into().to_string_lossy().into_owned()) },
2049 ),
2050 _ => unreachable!(),
2051 };
2052
2053 quote! {
2054 pub struct #option_builder_name<'a> {
2056 map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
2057 prefix: String,
2058 }
2059
2060 impl<'a> #option_builder_name<'a> {
2061 fn new(
2062 map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
2063 prefix: String,
2064 ) -> Self {
2065 Self { map, prefix }
2066 }
2067
2068 pub fn none(self) -> Self {
2070 self.map.insert(
2071 format!("{}.is_none", self.prefix),
2072 elicitor::ResponseValue::Bool(true),
2073 );
2074 self
2075 }
2076
2077 pub fn some(self, value: #some_param) -> Self {
2079 self.map.insert(self.prefix.clone(), #some_conversion);
2080 self
2081 }
2082 }
2083 }
2084 } else {
2085 let inner_builder_name = format_ident!("{}SuggestBuilder", inner_type_name);
2087
2088 quote! {
2089 pub struct #option_builder_name<'a> {
2091 map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
2092 prefix: String,
2093 }
2094
2095 impl<'a> #option_builder_name<'a> {
2096 fn new(
2097 map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
2098 prefix: String,
2099 ) -> Self {
2100 Self { map, prefix }
2101 }
2102
2103 pub fn none(self) -> Self {
2105 self.map.insert(
2106 format!("{}.is_none", self.prefix),
2107 elicitor::ResponseValue::Bool(true),
2108 );
2109 self
2110 }
2111
2112 pub fn some<F>(self, f: F) -> Self
2114 where
2115 F: FnOnce(#inner_builder_name<'_>) -> #inner_builder_name<'_>,
2116 {
2117 let builder = #inner_builder_name::new(self.map, self.prefix.clone());
2118 f(builder);
2119 self
2120 }
2121 }
2122 }
2123 }
2124}
2125
2126fn collect_option_builders(input: &DeriveInput) -> Vec<TokenStream2> {
2128 let mut option_types: Vec<Type> = Vec::new();
2129 let mut seen_names: std::collections::HashSet<String> = std::collections::HashSet::new();
2130 let mut builders = Vec::new();
2131
2132 match &input.data {
2134 Data::Struct(data) => {
2135 collect_option_types_from_fields(&data.fields, &mut option_types, &mut seen_names);
2136 }
2137 Data::Enum(data) => {
2138 for variant in &data.variants {
2139 collect_option_types_from_fields(
2140 &variant.fields,
2141 &mut option_types,
2142 &mut seen_names,
2143 );
2144 }
2145 }
2146 Data::Union(_) => {}
2147 }
2148
2149 for ty in option_types {
2151 builders.push(generate_option_builder(&ty));
2152 }
2153
2154 builders
2155}
2156
2157fn collect_option_types_from_fields(
2158 fields: &Fields,
2159 option_types: &mut Vec<Type>,
2160 seen_names: &mut std::collections::HashSet<String>,
2161) {
2162 match fields {
2163 Fields::Named(fields) => {
2164 for field in &fields.named {
2165 if let Some(inner) = extract_option_inner_type(&field.ty) {
2166 let name = type_to_string(&inner);
2167 if seen_names.insert(name) {
2168 option_types.push(inner);
2169 }
2170 }
2171 }
2172 }
2173 Fields::Unnamed(fields) => {
2174 for field in &fields.unnamed {
2175 if let Some(inner) = extract_option_inner_type(&field.ty) {
2176 let name = type_to_string(&inner);
2177 if seen_names.insert(name) {
2178 option_types.push(inner);
2179 }
2180 }
2181 }
2182 }
2183 Fields::Unit => {}
2184 }
2185}
2186
2187fn to_snake_case(s: &str) -> String {
2189 let mut result = String::new();
2190 for (i, c) in s.chars().enumerate() {
2191 if c.is_uppercase() {
2192 if i > 0 {
2193 result.push('_');
2194 }
2195 result.push(c.to_lowercase().next().unwrap());
2196 } else {
2197 result.push(c);
2198 }
2199 }
2200 result
2201}
2202
2203fn capitalize_first(s: &str) -> String {
2205 let mut chars = s.chars();
2206 match chars.next() {
2207 None => String::new(),
2208 Some(first) => first.to_uppercase().chain(chars).collect(),
2209 }
2210}
2211
2212fn generate_builder_methods_for_type(
2213 input: &DeriveInput,
2214 prefix: &str,
2215 suggest_methods: &mut Vec<TokenStream2>,
2216 assume_methods: &mut Vec<TokenStream2>,
2217) -> syn::Result<()> {
2218 match &input.data {
2219 Data::Struct(data) => {
2220 generate_builder_methods_for_fields(
2221 &data.fields,
2222 prefix,
2223 suggest_methods,
2224 assume_methods,
2225 )?;
2226 }
2227 Data::Enum(data) => {
2228 for variant in &data.variants {
2230 let variant_prefix = if prefix.is_empty() {
2231 variant.ident.to_string().to_lowercase()
2232 } else {
2233 format!("{}_{}", prefix, variant.ident.to_string().to_lowercase())
2234 };
2235 generate_builder_methods_for_fields(
2236 &variant.fields,
2237 &variant_prefix,
2238 suggest_methods,
2239 assume_methods,
2240 )?;
2241 }
2242 }
2243 Data::Union(_) => {}
2244 }
2245 Ok(())
2246}
2247
2248fn generate_builder_methods_for_fields(
2249 fields: &Fields,
2250 prefix: &str,
2251 suggest_methods: &mut Vec<TokenStream2>,
2252 assume_methods: &mut Vec<TokenStream2>,
2253) -> syn::Result<()> {
2254 match fields {
2255 Fields::Named(fields) => {
2256 for field in &fields.named {
2257 let field_name = field.ident.as_ref().unwrap();
2258 let field_name_str = field_name.to_string();
2259 let ty = &field.ty;
2260
2261 let method_suffix = if prefix.is_empty() {
2262 field_name_str.clone()
2263 } else {
2264 format!("{}_{}", prefix, field_name_str)
2265 };
2266
2267 let path_key = if prefix.is_empty() {
2268 field_name_str.clone()
2269 } else {
2270 format!("{}.{}", prefix.replace('_', "."), field_name_str)
2271 };
2272
2273 generate_suggest_assume_methods(
2274 &method_suffix,
2275 &path_key,
2276 ty,
2277 suggest_methods,
2278 assume_methods,
2279 );
2280 }
2281 }
2282 Fields::Unnamed(fields) => {
2283 for (i, field) in fields.unnamed.iter().enumerate() {
2284 let field_name_str = i.to_string();
2285 let ty = &field.ty;
2286
2287 let method_suffix = if prefix.is_empty() {
2288 field_name_str.clone()
2289 } else {
2290 format!("{}_{}", prefix, field_name_str)
2291 };
2292
2293 let path_key = if prefix.is_empty() {
2294 field_name_str
2295 } else {
2296 format!("{}.{}", prefix.replace('_', "."), i)
2297 };
2298
2299 generate_suggest_assume_methods(
2300 &method_suffix,
2301 &path_key,
2302 ty,
2303 suggest_methods,
2304 assume_methods,
2305 );
2306 }
2307 }
2308 Fields::Unit => {}
2309 }
2310 Ok(())
2311}
2312
2313fn generate_suggest_assume_methods(
2314 method_suffix: &str,
2315 path_key: &str,
2316 ty: &Type,
2317 suggest_methods: &mut Vec<TokenStream2>,
2318 assume_methods: &mut Vec<TokenStream2>,
2319) {
2320 let suggest_name = format_ident!("suggest_{}", method_suffix);
2321 let assume_name = format_ident!("assume_{}", method_suffix);
2322
2323 let type_name = type_to_string(ty);
2324
2325 if extract_vec_inner_type(ty).is_some() {
2327 return;
2328 }
2329
2330 if let Some(inner_ty) = extract_option_inner_type(ty) {
2332 let inner_type_name = type_to_string(&inner_ty);
2333 let option_builder_name =
2334 format_ident!("Option{}SuggestBuilder", capitalize_first(&inner_type_name));
2335
2336 suggest_methods.push(quote! {
2337 pub fn #suggest_name<F>(mut self, f: F) -> Self
2339 where
2340 F: FnOnce(#option_builder_name<'_>) -> #option_builder_name<'_>,
2341 {
2342 let builder = #option_builder_name::new(&mut self.suggestions, #path_key.to_string());
2343 f(builder);
2344 self
2345 }
2346 });
2347
2348 assume_methods.push(quote! {
2349 pub fn #assume_name<F>(mut self, f: F) -> Self
2351 where
2352 F: FnOnce(#option_builder_name<'_>) -> #option_builder_name<'_>,
2353 {
2354 let builder = #option_builder_name::new(&mut self.assumptions, #path_key.to_string());
2355 f(builder);
2356 self
2357 }
2358 });
2359 return;
2360 }
2361
2362 let (param_type, conversion) = match type_name.as_str() {
2364 "String" => (
2365 Some(quote! { impl Into<String> }),
2366 Some(quote! { elicitor::ResponseValue::String(value.into()) }),
2367 ),
2368 "bool" => (
2369 Some(quote! { bool }),
2370 Some(quote! { elicitor::ResponseValue::Bool(value) }),
2371 ),
2372 "i8" | "i16" | "i32" | "i64" | "isize" => (
2373 Some(quote! { #ty }),
2374 Some(quote! { elicitor::ResponseValue::Int(value as i64) }),
2375 ),
2376 "u8" | "u16" | "u32" | "u64" | "usize" => (
2377 Some(quote! { #ty }),
2378 Some(quote! { elicitor::ResponseValue::Int(value as i64) }),
2379 ),
2380 "f32" | "f64" => (
2381 Some(quote! { #ty }),
2382 Some(quote! { elicitor::ResponseValue::Float(value as f64) }),
2383 ),
2384 "PathBuf" => (
2385 Some(quote! { impl Into<std::path::PathBuf> }),
2386 Some(
2387 quote! { elicitor::ResponseValue::String(value.into().to_string_lossy().into_owned()) },
2388 ),
2389 ),
2390 _ => (None, None), };
2392
2393 if let (Some(param_type), Some(conversion)) = (param_type, conversion) {
2394 suggest_methods.push(quote! {
2396 pub fn #suggest_name(mut self, value: #param_type) -> Self {
2398 self.suggestions.insert(#path_key.to_string(), #conversion);
2399 self
2400 }
2401 });
2402
2403 assume_methods.push(quote! {
2404 pub fn #assume_name(mut self, value: #param_type) -> Self {
2406 self.assumptions.insert(#path_key.to_string(), #conversion);
2407 self
2408 }
2409 });
2410 } else {
2411 let inner_builder_name = format_ident!("{}SuggestBuilder", type_name);
2413
2414 suggest_methods.push(quote! {
2415 pub fn #suggest_name<F>(mut self, f: F) -> Self
2417 where
2418 F: FnOnce(#inner_builder_name<'_>) -> #inner_builder_name<'_>,
2419 {
2420 let builder = #inner_builder_name::new(&mut self.suggestions, #path_key.to_string());
2421 f(builder);
2422 self
2423 }
2424 });
2425
2426 assume_methods.push(quote! {
2427 pub fn #assume_name<F>(mut self, f: F) -> Self
2429 where
2430 F: FnOnce(#inner_builder_name<'_>) -> #inner_builder_name<'_>,
2431 {
2432 let builder = #inner_builder_name::new(&mut self.assumptions, #path_key.to_string());
2433 f(builder);
2434 self
2435 }
2436 });
2437 }
2438}
2439
2440fn generate_with_suggestions_struct(data: &syn::DataStruct) -> TokenStream2 {
2442 match &data.fields {
2443 Fields::Named(fields) => {
2444 let insertions: Vec<_> = fields
2445 .named
2446 .iter()
2447 .filter_map(|f| {
2448 let field_name = f.ident.as_ref()?;
2449 let field_name_str = field_name.to_string();
2450 let ty = &f.ty;
2451 let type_name = type_to_string(ty);
2452
2453 match type_name.as_str() {
2455 "String" => Some(quote! {
2456 self.suggestions.insert(
2457 #field_name_str.to_string(),
2458 elicitor::ResponseValue::String(instance.#field_name.clone())
2459 );
2460 }),
2461 "bool" => Some(quote! {
2462 self.suggestions.insert(
2463 #field_name_str.to_string(),
2464 elicitor::ResponseValue::Bool(instance.#field_name)
2465 );
2466 }),
2467 "i8" | "i16" | "i32" | "i64" | "isize" => Some(quote! {
2468 self.suggestions.insert(
2469 #field_name_str.to_string(),
2470 elicitor::ResponseValue::Int(instance.#field_name as i64)
2471 );
2472 }),
2473 "u8" | "u16" | "u32" | "u64" | "usize" => Some(quote! {
2474 self.suggestions.insert(
2475 #field_name_str.to_string(),
2476 elicitor::ResponseValue::Int(instance.#field_name as i64)
2477 );
2478 }),
2479 "f32" | "f64" => Some(quote! {
2480 self.suggestions.insert(
2481 #field_name_str.to_string(),
2482 elicitor::ResponseValue::Float(instance.#field_name as f64)
2483 );
2484 }),
2485 "PathBuf" => Some(quote! {
2486 self.suggestions.insert(
2487 #field_name_str.to_string(),
2488 elicitor::ResponseValue::String(instance.#field_name.display().to_string())
2489 );
2490 }),
2491 _ => None, }
2493 })
2494 .collect();
2495
2496 quote! { #(#insertions)* }
2497 }
2498 Fields::Unnamed(fields) => {
2499 let insertions: Vec<_> = fields
2500 .unnamed
2501 .iter()
2502 .enumerate()
2503 .filter_map(|(i, f)| {
2504 let idx = syn::Index::from(i);
2505 let field_name_str = i.to_string();
2506 let ty = &f.ty;
2507 let type_name = type_to_string(ty);
2508
2509 match type_name.as_str() {
2510 "String" => Some(quote! {
2511 self.suggestions.insert(
2512 #field_name_str.to_string(),
2513 elicitor::ResponseValue::String(instance.#idx.clone())
2514 );
2515 }),
2516 "bool" => Some(quote! {
2517 self.suggestions.insert(
2518 #field_name_str.to_string(),
2519 elicitor::ResponseValue::Bool(instance.#idx)
2520 );
2521 }),
2522 "i8" | "i16" | "i32" | "i64" | "isize" => Some(quote! {
2523 self.suggestions.insert(
2524 #field_name_str.to_string(),
2525 elicitor::ResponseValue::Int(instance.#idx as i64)
2526 );
2527 }),
2528 "u8" | "u16" | "u32" | "u64" | "usize" => Some(quote! {
2529 self.suggestions.insert(
2530 #field_name_str.to_string(),
2531 elicitor::ResponseValue::Int(instance.#idx as i64)
2532 );
2533 }),
2534 "f32" | "f64" => Some(quote! {
2535 self.suggestions.insert(
2536 #field_name_str.to_string(),
2537 elicitor::ResponseValue::Float(instance.#idx as f64)
2538 );
2539 }),
2540 _ => None,
2541 }
2542 })
2543 .collect();
2544
2545 quote! { #(#insertions)* }
2546 }
2547 Fields::Unit => quote! {},
2548 }
2549}
2550
2551fn generate_with_suggestions_body(input: &DeriveInput) -> TokenStream2 {
2553 match &input.data {
2554 Data::Struct(data) => generate_with_suggestions_struct(data),
2555 Data::Enum(_) => {
2556 quote! {}
2558 }
2559 Data::Union(_) => quote! {},
2560 }
2561}