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