1use proc_macro::TokenStream;
36use proc_macro2::TokenStream as TokenStream2;
37use quote::quote;
38use syn::{
39 Data, DeriveInput, Fields, FieldsNamed, Generics, Ident, LitStr, Type, Variant,
40 parse_macro_input,
41};
42
43#[proc_macro_derive(ToValue, attributes(fig))]
44pub fn derive_to_value(input: TokenStream) -> TokenStream {
45 let input = parse_macro_input!(input as DeriveInput);
46 expand_to_value(&input)
47 .unwrap_or_else(syn::Error::into_compile_error)
48 .into()
49}
50
51#[proc_macro_derive(FromValue, attributes(fig))]
52pub fn derive_from_value(input: TokenStream) -> TokenStream {
53 let input = parse_macro_input!(input as DeriveInput);
54 expand_from_value(&input)
55 .unwrap_or_else(syn::Error::into_compile_error)
56 .into()
57}
58
59#[derive(Clone, Copy)]
69enum RenameRule {
70 Lower,
71 Upper,
72 Pascal,
73 Camel,
74 Snake,
75 ScreamingSnake,
76 Kebab,
77 ScreamingKebab,
78}
79
80impl RenameRule {
81 fn from_str(s: &str) -> Result<Self, String> {
82 Ok(match s {
83 "lowercase" => RenameRule::Lower,
84 "UPPERCASE" => RenameRule::Upper,
85 "PascalCase" => RenameRule::Pascal,
86 "camelCase" => RenameRule::Camel,
87 "snake_case" => RenameRule::Snake,
88 "SCREAMING_SNAKE_CASE" => RenameRule::ScreamingSnake,
89 "kebab-case" => RenameRule::Kebab,
90 "SCREAMING-KEBAB-CASE" => RenameRule::ScreamingKebab,
91 other => {
92 return Err(format!(
93 "unknown `rename_all` rule `{other}` (expected one of: lowercase, \
94 UPPERCASE, PascalCase, camelCase, snake_case, SCREAMING_SNAKE_CASE, \
95 kebab-case, SCREAMING-KEBAB-CASE)"
96 ));
97 }
98 })
99 }
100
101 fn apply_to_field(self, field: &str) -> String {
103 match self {
104 RenameRule::Lower | RenameRule::Snake => field.to_owned(),
105 RenameRule::Upper | RenameRule::ScreamingSnake => field.to_ascii_uppercase(),
106 RenameRule::Pascal => {
107 let mut out = String::new();
108 let mut capitalize = true;
109 for ch in field.chars() {
110 if ch == '_' {
111 capitalize = true;
112 } else if capitalize {
113 out.push(ch.to_ascii_uppercase());
114 capitalize = false;
115 } else {
116 out.push(ch);
117 }
118 }
119 out
120 }
121 RenameRule::Camel => {
122 let pascal = RenameRule::Pascal.apply_to_field(field);
123 match pascal.char_indices().nth(1) {
124 Some((i, _)) => pascal[..1].to_ascii_lowercase() + &pascal[i..],
125 None => pascal.to_ascii_lowercase(),
126 }
127 }
128 RenameRule::Kebab => field.replace('_', "-"),
129 RenameRule::ScreamingKebab => field.to_ascii_uppercase().replace('_', "-"),
130 }
131 }
132
133 fn apply_to_variant(self, variant: &str) -> String {
135 match self {
136 RenameRule::Pascal => variant.to_owned(),
137 RenameRule::Lower => variant.to_ascii_lowercase(),
138 RenameRule::Upper => variant.to_ascii_uppercase(),
139 RenameRule::Camel => match variant.char_indices().nth(1) {
140 Some((i, _)) => variant[..1].to_ascii_lowercase() + &variant[i..],
141 None => variant.to_ascii_lowercase(),
142 },
143 RenameRule::Snake => {
144 let mut out = String::new();
145 for (i, ch) in variant.char_indices() {
146 if i > 0 && ch.is_uppercase() {
147 out.push('_');
148 }
149 out.push(ch.to_ascii_lowercase());
150 }
151 out
152 }
153 RenameRule::ScreamingSnake => {
154 RenameRule::Snake.apply_to_variant(variant).to_ascii_uppercase()
155 }
156 RenameRule::Kebab => RenameRule::Snake.apply_to_variant(variant).replace('_', "-"),
157 RenameRule::ScreamingKebab => RenameRule::ScreamingSnake
158 .apply_to_variant(variant)
159 .replace('_', "-"),
160 }
161 }
162}
163
164#[derive(Default)]
166struct FieldAttrs {
167 rename: Option<String>,
168 skip: bool,
169 flatten: bool,
170 default: bool,
171 default_path: Option<syn::Path>,
174 skip_serializing_if: Option<syn::Path>,
177 deserialize_with: Option<syn::Path>,
181 aliases: Vec<String>,
184}
185
186fn parse_field_attrs(attrs: &[syn::Attribute]) -> syn::Result<FieldAttrs> {
187 let mut parsed = FieldAttrs::default();
188 for attr in attrs {
189 if !attr.path().is_ident("fig") {
190 continue;
191 }
192 attr.parse_nested_meta(|meta| {
193 if meta.path.is_ident("rename") {
194 parsed.rename = Some(meta.value()?.parse::<LitStr>()?.value());
195 } else if meta.path.is_ident("skip") {
196 parsed.skip = true;
197 } else if meta.path.is_ident("flatten") {
198 parsed.flatten = true;
199 } else if meta.path.is_ident("default") {
200 if let Ok(value) = meta.value() {
202 parsed.default_path = Some(value.parse::<LitStr>()?.parse::<syn::Path>()?);
203 } else {
204 parsed.default = true;
205 }
206 } else if meta.path.is_ident("skip_serializing_if") {
207 let path = meta.value()?.parse::<LitStr>()?.parse::<syn::Path>()?;
208 parsed.skip_serializing_if = Some(path);
209 } else if meta.path.is_ident("deserialize_with") {
210 let path = meta.value()?.parse::<LitStr>()?.parse::<syn::Path>()?;
211 parsed.deserialize_with = Some(path);
212 } else if meta.path.is_ident("alias") {
213 parsed.aliases.push(meta.value()?.parse::<LitStr>()?.value());
214 } else {
215 return Err(meta.error(
216 "unknown `fig` field attribute (expected: rename, skip, flatten, \
217 default, skip_serializing_if, deserialize_with, alias)",
218 ));
219 }
220 Ok(())
221 })?;
222 }
223 Ok(parsed)
224}
225
226#[derive(Default)]
228struct VariantAttrs {
229 rename: Option<String>,
230}
231
232fn parse_variant_attrs(attrs: &[syn::Attribute]) -> syn::Result<VariantAttrs> {
233 let mut parsed = VariantAttrs::default();
234 for attr in attrs {
235 if !attr.path().is_ident("fig") {
236 continue;
237 }
238 attr.parse_nested_meta(|meta| {
239 if meta.path.is_ident("rename") {
240 parsed.rename = Some(meta.value()?.parse::<LitStr>()?.value());
241 } else {
242 return Err(meta.error("unknown `fig` variant attribute (expected: rename)"));
243 }
244 Ok(())
245 })?;
246 }
247 Ok(parsed)
248}
249
250#[derive(Default)]
252struct ContainerAttrs {
253 rename_all: Option<RenameRule>,
254 tag: Option<String>,
255 content: Option<String>,
256 untagged: bool,
257}
258
259fn parse_container_attrs(attrs: &[syn::Attribute]) -> syn::Result<ContainerAttrs> {
260 let mut parsed = ContainerAttrs::default();
261 for attr in attrs {
262 if !attr.path().is_ident("fig") {
263 continue;
264 }
265 attr.parse_nested_meta(|meta| {
266 if meta.path.is_ident("rename_all") {
267 let lit = meta.value()?.parse::<LitStr>()?;
268 let rule = RenameRule::from_str(&lit.value())
269 .map_err(|msg| syn::Error::new(lit.span(), msg))?;
270 parsed.rename_all = Some(rule);
271 } else if meta.path.is_ident("tag") {
272 parsed.tag = Some(meta.value()?.parse::<LitStr>()?.value());
273 } else if meta.path.is_ident("content") {
274 parsed.content = Some(meta.value()?.parse::<LitStr>()?.value());
275 } else if meta.path.is_ident("untagged") {
276 parsed.untagged = true;
277 } else {
278 return Err(meta.error(
279 "unknown `fig` container attribute (expected: rename_all, tag, content, untagged)",
280 ));
281 }
282 Ok(())
283 })?;
284 }
285 Ok(parsed)
286}
287
288fn container_rename_all(attrs: &[syn::Attribute]) -> syn::Result<Option<RenameRule>> {
290 Ok(parse_container_attrs(attrs)?.rename_all)
291}
292
293enum Tagging {
295 External,
296 Internal(String),
297 Adjacent(String, String),
298 Untagged,
299}
300
301fn tagging_of(input: &DeriveInput) -> syn::Result<Tagging> {
302 let c = parse_container_attrs(&input.attrs)?;
303 match (c.untagged, c.tag, c.content) {
304 (true, None, None) => Ok(Tagging::Untagged),
305 (true, _, _) => Err(syn::Error::new_spanned(
306 input,
307 "`#[fig(untagged)]` cannot be combined with `tag`/`content`",
308 )),
309 (false, Some(tag), Some(content)) => Ok(Tagging::Adjacent(tag, content)),
310 (false, Some(tag), None) => Ok(Tagging::Internal(tag)),
311 (false, None, Some(_)) => Err(syn::Error::new_spanned(
312 input,
313 "`#[fig(content = ..)]` requires `#[fig(tag = ..)]`",
314 )),
315 (false, None, None) => Ok(Tagging::External),
316 }
317}
318
319struct FieldInfo<'a> {
324 ident: &'a Ident,
325 ty: &'a Type,
326 key: String,
328 skip: bool,
329 flatten: bool,
330 use_default: bool,
332 default_path: Option<syn::Path>,
335 skip_serializing_if: Option<syn::Path>,
338 deserialize_with: Option<syn::Path>,
340 aliases: Vec<String>,
342}
343
344fn collect_named_fields(
348 fields: &FieldsNamed,
349 rename_all: Option<RenameRule>,
350) -> syn::Result<Vec<FieldInfo<'_>>> {
351 let mut infos = Vec::with_capacity(fields.named.len());
352 for field in &fields.named {
353 let attrs = parse_field_attrs(&field.attrs)?;
354 if attrs.flatten && attrs.rename.is_some() {
355 return Err(syn::Error::new_spanned(
356 field,
357 "`#[fig(flatten)]` and `#[fig(rename)]` are mutually exclusive",
358 ));
359 }
360 let ident = field.ident.as_ref().expect("named field has an ident");
361 let key = match attrs.rename {
362 Some(explicit) => explicit,
363 None => match rename_all {
364 Some(rule) => rule.apply_to_field(&ident.to_string()),
365 None => ident.to_string(),
366 },
367 };
368 let use_default = attrs.default || attrs.default_path.is_some() || is_option(&field.ty);
369 infos.push(FieldInfo {
370 ident,
371 ty: &field.ty,
372 key,
373 skip: attrs.skip,
374 flatten: attrs.flatten,
375 use_default,
376 default_path: attrs.default_path,
377 skip_serializing_if: attrs.skip_serializing_if,
378 deserialize_with: attrs.deserialize_with,
379 aliases: attrs.aliases,
380 });
381 }
382 Ok(infos)
383}
384
385fn is_option(ty: &Type) -> bool {
388 matches!(ty, Type::Path(tp) if tp.qself.is_none()
389 && tp.path.segments.last().is_some_and(|s| s.ident == "Option"))
390}
391
392fn bounded_where(generics: &Generics, bound: TokenStream2) -> TokenStream2 {
394 let mut preds: Vec<TokenStream2> = Vec::new();
395 if let Some(existing) = &generics.where_clause {
396 for p in &existing.predicates {
397 preds.push(quote!(#p));
398 }
399 }
400 for tp in generics.type_params() {
401 let id = &tp.ident;
402 preds.push(quote!(#id: #bound));
403 }
404 if preds.is_empty() {
405 quote!()
406 } else {
407 quote!(where #(#preds),*)
408 }
409}
410
411fn variant_key(variant: &Variant, rename_all: Option<RenameRule>) -> syn::Result<String> {
414 let attrs = parse_variant_attrs(&variant.attrs)?;
415 Ok(match attrs.rename {
416 Some(explicit) => explicit,
417 None => match rename_all {
418 Some(rule) => rule.apply_to_variant(&variant.ident.to_string()),
419 None => variant.ident.to_string(),
420 },
421 })
422}
423
424fn expand_to_value(input: &DeriveInput) -> syn::Result<TokenStream2> {
429 let name = &input.ident;
430 let (impl_g, ty_g, _) = input.generics.split_for_impl();
431 let where_clause = bounded_where(&input.generics, quote!(fig::ToValue));
432
433 let body = match &input.data {
434 Data::Struct(s) => to_value_struct(&s.fields, input)?,
435 Data::Enum(e) => to_value_enum(input, e)?,
436 Data::Union(_) => {
437 return Err(syn::Error::new_spanned(
438 input,
439 "fig's ToValue derive does not support unions",
440 ));
441 }
442 };
443
444 Ok(quote! {
445 impl #impl_g fig::ToValue for #name #ty_g #where_clause {
446 fn to_value(&self) -> fig::Value {
447 #body
448 }
449 }
450 })
451}
452
453fn to_value_struct(fields: &Fields, input: &DeriveInput) -> syn::Result<TokenStream2> {
454 match fields {
455 Fields::Named(named) => {
456 let infos = collect_named_fields(named, container_rename_all(&input.attrs)?)?;
457 let stmts = infos.iter().filter(|f| !f.skip).map(|f| {
458 let ident = f.ident;
459 if f.flatten {
460 quote! {
461 if let fig::Value::Map(mut __m) = fig::ToValue::to_value(&self.#ident) {
462 __entries.append(&mut __m);
463 }
464 }
465 } else {
466 let key = &f.key;
467 let push = quote! {
468 __entries.push((
469 fig::Value::Str(::std::string::String::from(#key)),
470 fig::ToValue::to_value(&self.#ident),
471 ));
472 };
473 match &f.skip_serializing_if {
474 Some(pred) => quote! {
475 if !#pred(&self.#ident) { #push }
476 },
477 None => push,
478 }
479 }
480 });
481 Ok(quote! {
482 let mut __entries: ::std::vec::Vec<(fig::Value, fig::Value)> = ::std::vec::Vec::new();
483 #(#stmts)*
484 fig::Value::Map(__entries)
485 })
486 }
487 Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
488 Ok(quote! { fig::ToValue::to_value(&self.0) })
489 }
490 Fields::Unnamed(_) => Err(syn::Error::new_spanned(
491 input,
492 "fig's ToValue derive supports newtype structs (one field) but not multi-field tuple structs yet",
493 )),
494 Fields::Unit => Ok(quote! { fig::Value::Null }),
495 }
496}
497
498fn to_value_enum(input: &DeriveInput, data: &syn::DataEnum) -> syn::Result<TokenStream2> {
499 let tagging = tagging_of(input)?;
500 let rename_all = container_rename_all(&input.attrs)?;
501 let mut arms = Vec::with_capacity(data.variants.len());
502 for variant in &data.variants {
503 arms.push(to_value_variant_arm(variant, &tagging, rename_all)?);
504 }
505 Ok(quote! {
506 match self {
507 #(#arms)*
508 }
509 })
510}
511
512fn to_value_variant_arm(
514 variant: &Variant,
515 tagging: &Tagging,
516 rename_all: Option<RenameRule>,
517) -> syn::Result<TokenStream2> {
518 let vident = &variant.ident;
519 let key = variant_key(variant, rename_all)?;
520 let key_value = quote! { fig::Value::Str(::std::string::String::from(#key)) };
521
522 let (pattern, content): (TokenStream2, Option<TokenStream2>) = match &variant.fields {
524 Fields::Unit => (quote! { Self::#vident }, None),
525 Fields::Unnamed(u) if u.unnamed.len() == 1 => (
526 quote! { Self::#vident(__f0) },
527 Some(quote! { fig::ToValue::to_value(__f0) }),
528 ),
529 Fields::Unnamed(u) => {
530 let binds: Vec<Ident> = (0..u.unnamed.len())
531 .map(|i| Ident::new(&format!("__f{i}"), vident.span()))
532 .collect();
533 (
534 quote! { Self::#vident( #(#binds),* ) },
535 Some(quote! { fig::Value::Seq(vec![ #(fig::ToValue::to_value(#binds)),* ]) }),
536 )
537 }
538 Fields::Named(named) => {
539 let infos = collect_named_fields(named, None)?;
543 if let Some(f) = infos.iter().find(|f| f.flatten) {
544 return Err(syn::Error::new_spanned(
545 f.ident,
546 "`#[fig(flatten)]` is not supported inside enum variants yet",
547 ));
548 }
549 let binds: Vec<Ident> = infos
550 .iter()
551 .map(|f| Ident::new(&format!("__f_{}", f.ident), f.ident.span()))
552 .collect();
553 let pat_fields = infos.iter().zip(&binds).map(|(f, b)| {
554 let id = f.ident;
555 quote! { #id: #b }
556 });
557 let entry_stmts = infos.iter().zip(&binds).filter(|(f, _)| !f.skip).map(|(f, b)| {
558 let fkey = &f.key;
559 let push = quote! {
560 __vmap.push((
561 fig::Value::Str(::std::string::String::from(#fkey)),
562 fig::ToValue::to_value(#b),
563 ));
564 };
565 match &f.skip_serializing_if {
566 Some(pred) => quote! { if !#pred(#b) { #push } },
567 None => push,
568 }
569 });
570 (
571 quote! { Self::#vident { #(#pat_fields),* } },
572 Some(quote! {{
573 let mut __vmap: ::std::vec::Vec<(fig::Value, fig::Value)> =
574 ::std::vec::Vec::new();
575 #(#entry_stmts)*
576 fig::Value::Map(__vmap)
577 }}),
578 )
579 }
580 };
581
582 let is_tuple_multi = matches!(&variant.fields, Fields::Unnamed(u) if u.unnamed.len() > 1);
583
584 let body = match tagging {
585 Tagging::External => match &content {
586 None => quote! { #key_value },
587 Some(c) => quote! { fig::Value::Map(vec![(#key_value, #c)]) },
588 },
589 Tagging::Adjacent(tag, content_key) => match &content {
590 None => quote! {
591 fig::Value::Map(vec![(
592 fig::Value::Str(::std::string::String::from(#tag)),
593 #key_value,
594 )])
595 },
596 Some(c) => quote! {
597 fig::Value::Map(vec![
598 (fig::Value::Str(::std::string::String::from(#tag)), #key_value),
599 (fig::Value::Str(::std::string::String::from(#content_key)), #c),
600 ])
601 },
602 },
603 Tagging::Internal(tag) => {
604 if is_tuple_multi {
605 return Err(syn::Error::new_spanned(
606 variant,
607 "internally tagged enums do not support tuple variants (matching serde); use adjacent or external tagging",
608 ));
609 }
610 match &content {
611 None => quote! {
612 fig::Value::Map(vec![(
613 fig::Value::Str(::std::string::String::from(#tag)),
614 #key_value,
615 )])
616 },
617 Some(c) => quote! {
621 {
622 let mut __entries: ::std::vec::Vec<(fig::Value, fig::Value)> = vec![(
623 fig::Value::Str(::std::string::String::from(#tag)),
624 #key_value,
625 )];
626 if let fig::Value::Map(mut __m) = #c {
627 __entries.append(&mut __m);
628 }
629 fig::Value::Map(__entries)
630 }
631 },
632 }
633 }
634 Tagging::Untagged => match &content {
635 None => quote! { fig::Value::Null },
636 Some(c) => quote! { #c },
637 },
638 };
639
640 Ok(quote! { #pattern => #body, })
641}
642
643fn expand_from_value(input: &DeriveInput) -> syn::Result<TokenStream2> {
648 let name = &input.ident;
649 let (impl_g, ty_g, _) = input.generics.split_for_impl();
650 let where_clause = bounded_where(&input.generics, quote!(fig::FromValue));
651
652 let body = match &input.data {
653 Data::Struct(s) => from_value_struct(&s.fields, name, input)?,
654 Data::Enum(e) => from_value_enum(input, e)?,
655 Data::Union(_) => {
656 return Err(syn::Error::new_spanned(
657 input,
658 "fig's FromValue derive does not support unions",
659 ));
660 }
661 };
662
663 Ok(quote! {
664 impl #impl_g fig::FromValue for #name #ty_g #where_clause {
665 fn from_value(value: &fig::Value) -> ::core::result::Result<Self, fig::Error> {
666 #body
667 }
668 }
669 })
670}
671
672fn from_value_struct(
673 fields: &Fields,
674 name: &Ident,
675 input: &DeriveInput,
676) -> syn::Result<TokenStream2> {
677 match fields {
678 Fields::Named(named) => from_map_named(
679 named,
680 "e! { Self },
681 "e! { value },
682 &name.to_string(),
683 true,
684 container_rename_all(&input.attrs)?,
685 ),
686 Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
687 let ty = &unnamed.unnamed[0].ty;
688 Ok(quote! {
689 ::core::result::Result::Ok(Self(<#ty as fig::FromValue>::from_value(value)?))
690 })
691 }
692 Fields::Unnamed(_) => Err(syn::Error::new_spanned(
693 input,
694 "fig's FromValue derive supports newtype structs (one field) but not multi-field tuple structs yet",
695 )),
696 Fields::Unit => Ok(quote! { ::core::result::Result::Ok(Self) }),
697 }
698}
699
700fn from_map_named(
704 fields: &FieldsNamed,
705 ctor: &TokenStream2,
706 map_value: &TokenStream2,
707 type_label: &str,
708 allow_flatten: bool,
709 rename_all: Option<RenameRule>,
710) -> syn::Result<TokenStream2> {
711 let infos = collect_named_fields(fields, rename_all)?;
712 if !allow_flatten && let Some(f) = infos.iter().find(|f| f.flatten) {
713 return Err(syn::Error::new_spanned(
714 f.ident,
715 "`#[fig(flatten)]` is not supported inside enum variants yet",
716 ));
717 }
718
719 let known_keys: Vec<&String> = infos
720 .iter()
721 .filter(|f| !f.skip && !f.flatten)
722 .map(|f| &f.key)
723 .collect();
724 let has_flatten = infos.iter().any(|f| f.flatten && !f.skip);
725
726 let rest = if has_flatten {
727 quote! {
728 const __KNOWN: &[&str] = &[#(#known_keys),*];
729 let mut __rest: ::std::vec::Vec<(fig::Value, fig::Value)> = ::std::vec::Vec::new();
730 for (__k, __v) in __entries.iter() {
731 let __consumed = matches!(__k, fig::Value::Str(__s) if __KNOWN.contains(&__s.as_str()));
732 if !__consumed {
733 __rest.push((__k.clone(), __v.clone()));
734 }
735 }
736 let __rest = fig::Value::Map(__rest);
737 }
738 } else {
739 quote! {}
740 };
741
742 let field_lets = infos.iter().map(|f| {
743 let ident = f.ident;
744 let ty = f.ty;
745 if f.skip {
746 return quote! { let #ident: #ty = ::core::default::Default::default(); };
747 }
748 if f.flatten {
749 return quote! {
750 let #ident: #ty = <#ty as fig::FromValue>::from_value(&__rest)?;
751 };
752 }
753 let key = &f.key;
754
755 if f.aliases.is_empty() && f.deserialize_with.is_none() && f.default_path.is_none() {
761 if f.use_default {
762 return quote! { let #ident: #ty = fig::field_or_default(__entries, #key)?; };
763 }
764 return quote! { let #ident: #ty = fig::field(__entries, #key, #type_label)?; };
765 }
766
767 let missing = match &f.default_path {
768 Some(path) => quote! { #path() },
769 None if f.use_default => quote! { ::core::default::Default::default() },
770 None => {
771 quote! { return ::core::result::Result::Err(fig::Error::missing_field(#key, #type_label)) }
772 }
773 };
774 let present = match &f.deserialize_with {
775 Some(path) => quote! { #path(__v)? },
776 None => quote! { <#ty as fig::FromValue>::from_value(__v)? },
777 };
778 let aliases = &f.aliases;
779 quote! {
780 let #ident: #ty = match fig::map_get(__entries, #key)
781 #(.or_else(|| fig::map_get(__entries, #aliases)))*
782 {
783 ::std::option::Option::Some(__v) => #present,
784 ::std::option::Option::None => #missing,
785 };
786 }
787 });
788
789 let field_names = infos.iter().map(|f| f.ident);
790
791 Ok(quote! {{
792 let __entries = match #map_value {
793 fig::Value::Map(__e) => __e,
794 _ => return ::core::result::Result::Err(
795 fig::Error::expected_mapping(#type_label),
796 ),
797 };
798 let _ = &__entries;
799 #rest
800 #(#field_lets)*
801 ::core::result::Result::Ok(#ctor { #(#field_names),* })
802 }})
803}
804
805fn build_variant(
809 variant: &Variant,
810 value_expr: &TokenStream2,
811 label: &str,
812) -> syn::Result<TokenStream2> {
813 let vident = &variant.ident;
814 match &variant.fields {
815 Fields::Unit => Ok(quote! { ::core::result::Result::Ok(Self::#vident) }),
816 Fields::Unnamed(u) if u.unnamed.len() == 1 => {
817 let ty = &u.unnamed[0].ty;
818 Ok(quote! {
819 ::core::result::Result::Ok(Self::#vident(<#ty as fig::FromValue>::from_value(#value_expr)?))
820 })
821 }
822 Fields::Unnamed(u) => {
823 let tys: Vec<&Type> = u.unnamed.iter().map(|f| &f.ty).collect();
824 let idxs: Vec<usize> = (0..tys.len()).collect();
825 let n = tys.len();
826 let seq_msg = format!("expected a sequence for tuple variant `{label}`");
827 Ok(quote! {{
828 let __items = match #value_expr {
829 fig::Value::Seq(__s) => __s,
830 _ => return ::core::result::Result::Err(
831 fig::Error::msg_static(#seq_msg),
832 ),
833 };
834 if __items.len() != #n {
835 return ::core::result::Result::Err(
836 fig::Error::wrong_seq_len(#label, #n, __items.len()),
837 );
838 }
839 ::core::result::Result::Ok(Self::#vident(
840 #(<#tys as fig::FromValue>::from_value(&__items[#idxs])?),*
841 ))
842 }})
843 }
844 Fields::Named(named) => from_map_named(
845 named,
846 "e! { Self::#vident },
847 value_expr,
848 label,
849 false,
850 None,
851 ),
852 }
853}
854
855fn from_value_enum(input: &DeriveInput, data: &syn::DataEnum) -> syn::Result<TokenStream2> {
856 let tagging = tagging_of(input)?;
857 let rename_all = container_rename_all(&input.attrs)?;
858 let enum_name = input.ident.to_string();
859 match tagging {
860 Tagging::External => from_value_external(data, &enum_name, rename_all),
861 Tagging::Internal(tag) => from_value_internal(data, &enum_name, &tag, rename_all),
862 Tagging::Adjacent(tag, content) => {
863 from_value_adjacent(data, &enum_name, &tag, &content, rename_all)
864 }
865 Tagging::Untagged => from_value_untagged(data, &enum_name),
866 }
867}
868
869fn from_value_external(
870 data: &syn::DataEnum,
871 enum_name: &str,
872 rename_all: Option<RenameRule>,
873) -> syn::Result<TokenStream2> {
874 let mut unit_arms = Vec::new();
875 let mut map_arms = Vec::new();
876 for variant in &data.variants {
877 let vident = &variant.ident;
878 let key = variant_key(variant, rename_all)?;
879 let label = format!("{enum_name}::{vident}");
880 if matches!(variant.fields, Fields::Unit) {
881 unit_arms.push(quote! { #key => ::core::result::Result::Ok(Self::#vident), });
882 } else {
883 let body = build_variant(variant, "e! { __v }, &label)?;
884 map_arms.push(quote! { #key => #body, });
885 }
886 }
887 let expected = format!("expected a string or single-key mapping for enum `{enum_name}`");
888 Ok(quote! {
889 match value {
890 fig::Value::Str(__s) => match __s.as_str() {
891 #(#unit_arms)*
892 __other => ::core::result::Result::Err(fig::Error::unknown_variant(#enum_name, __other)),
893 },
894 fig::Value::Map(__entries) if __entries.len() == 1 => {
895 let (__k, __v) = &__entries[0];
896 let __name = match __k {
897 fig::Value::Str(__s) => __s.as_str(),
898 _ => return ::core::result::Result::Err(
899 fig::Error::msg_static("enum variant key must be a string"),
900 ),
901 };
902 match __name {
903 #(#map_arms)*
904 __other => ::core::result::Result::Err(fig::Error::unknown_variant(#enum_name, __other)),
905 }
906 }
907 _ => ::core::result::Result::Err(fig::Error::msg_static(#expected)),
908 }
909 })
910}
911
912fn tag_prologue(enum_name: &str, tag: &str) -> TokenStream2 {
915 let not_map = format!("expected a mapping for tagged enum `{enum_name}`");
916 let missing_tag = format!("missing tag `{tag}` for enum `{enum_name}`");
917 let tag_kind = format!("tag `{tag}` for enum `{enum_name}` must be a string");
918 quote! {
919 let __entries = match value {
920 fig::Value::Map(__e) => __e,
921 _ => return ::core::result::Result::Err(
922 fig::Error::msg_static(#not_map),
923 ),
924 };
925 let __tag = match __entries.iter().rev().find_map(|(__k, __v)| match __k {
926 fig::Value::Str(__s) if __s == #tag => ::std::option::Option::Some(__v),
927 _ => ::std::option::Option::None,
928 }) {
929 ::std::option::Option::Some(fig::Value::Str(__s)) => __s.as_str(),
930 ::std::option::Option::Some(_) => return ::core::result::Result::Err(
931 fig::Error::msg_static(#tag_kind),
932 ),
933 ::std::option::Option::None => return ::core::result::Result::Err(
934 fig::Error::msg_static(#missing_tag),
935 ),
936 };
937 }
938}
939
940fn from_value_internal(
941 data: &syn::DataEnum,
942 enum_name: &str,
943 tag: &str,
944 rename_all: Option<RenameRule>,
945) -> syn::Result<TokenStream2> {
946 let mut arms = Vec::new();
947 for variant in &data.variants {
948 let vident = &variant.ident;
949 let key = variant_key(variant, rename_all)?;
950 let label = format!("{enum_name}::{vident}");
951 let arm = match &variant.fields {
952 Fields::Unit => quote! { #key => ::core::result::Result::Ok(Self::#vident), },
953 Fields::Named(named) => {
954 let body = from_map_named(
956 named,
957 "e! { Self::#vident },
958 "e! { value },
959 &label,
960 false,
961 None,
962 )?;
963 quote! { #key => #body, }
964 }
965 Fields::Unnamed(u) if u.unnamed.len() == 1 => {
966 let ty = &u.unnamed[0].ty;
968 quote! {
969 #key => {
970 let mut __rest: ::std::vec::Vec<(fig::Value, fig::Value)> = ::std::vec::Vec::new();
971 for (__k, __v) in __entries.iter() {
972 let __is_tag = matches!(__k, fig::Value::Str(__s) if __s == #tag);
973 if !__is_tag {
974 __rest.push((__k.clone(), __v.clone()));
975 }
976 }
977 ::core::result::Result::Ok(Self::#vident(
978 <#ty as fig::FromValue>::from_value(&fig::Value::Map(__rest))?,
979 ))
980 }
981 }
982 }
983 Fields::Unnamed(_) => {
984 return Err(syn::Error::new_spanned(
985 variant,
986 "internally tagged enums do not support tuple variants (matching serde)",
987 ));
988 }
989 };
990 arms.push(arm);
991 }
992 let prologue = tag_prologue(enum_name, tag);
993 Ok(quote! {
994 #prologue
995 match __tag {
996 #(#arms)*
997 __other => ::core::result::Result::Err(fig::Error::unknown_variant(#enum_name, __other)),
998 }
999 })
1000}
1001
1002fn from_value_adjacent(
1003 data: &syn::DataEnum,
1004 enum_name: &str,
1005 tag: &str,
1006 content: &str,
1007 rename_all: Option<RenameRule>,
1008) -> syn::Result<TokenStream2> {
1009 let mut arms = Vec::new();
1010 for variant in &data.variants {
1011 let vident = &variant.ident;
1012 let key = variant_key(variant, rename_all)?;
1013 let label = format!("{enum_name}::{vident}");
1014 if matches!(variant.fields, Fields::Unit) {
1015 arms.push(quote! { #key => ::core::result::Result::Ok(Self::#vident), });
1016 } else {
1017 let body = build_variant(variant, "e! { __content_val }, &label)?;
1018 let missing = format!("missing content `{content}` for variant `{label}`");
1019 arms.push(quote! {
1020 #key => {
1021 let __content_val = match __content {
1022 ::std::option::Option::Some(__c) => __c,
1023 ::std::option::Option::None => return ::core::result::Result::Err(
1024 fig::Error::msg_static(#missing),
1025 ),
1026 };
1027 #body
1028 }
1029 });
1030 }
1031 }
1032 let prologue = tag_prologue(enum_name, tag);
1033 Ok(quote! {
1034 #prologue
1035 let __content: ::std::option::Option<&fig::Value> =
1036 __entries.iter().rev().find_map(|(__k, __v)| match __k {
1037 fig::Value::Str(__s) if __s == #content => ::std::option::Option::Some(__v),
1038 _ => ::std::option::Option::None,
1039 });
1040 match __tag {
1041 #(#arms)*
1042 __other => ::core::result::Result::Err(fig::Error::unknown_variant(#enum_name, __other)),
1043 }
1044 })
1045}
1046
1047fn from_value_untagged(data: &syn::DataEnum, enum_name: &str) -> syn::Result<TokenStream2> {
1048 let mut attempts = Vec::new();
1049 for variant in &data.variants {
1050 let vident = &variant.ident;
1051 let label = format!("{enum_name}::{vident}");
1052 if matches!(variant.fields, Fields::Unit) {
1053 attempts.push(quote! {
1054 if matches!(value, fig::Value::Null) {
1055 return ::core::result::Result::Ok(Self::#vident);
1056 }
1057 });
1058 } else {
1059 let body = build_variant(variant, "e! { value }, &label)?;
1060 attempts.push(quote! {
1061 if let ::core::result::Result::Ok(__v) =
1062 (|| -> ::core::result::Result<Self, fig::Error> { #body })()
1063 {
1064 return ::core::result::Result::Ok(__v);
1065 }
1066 });
1067 }
1068 }
1069 let none = format!("no variant of enum `{enum_name}` matched the value");
1070 Ok(quote! {
1071 #(#attempts)*
1072 ::core::result::Result::Err(fig::Error::msg_static(#none))
1073 })
1074}