1use std::fmt::Display;
233
234use heck::{
235 ToKebabCase, ToLowerCamelCase, ToPascalCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase,
236};
237use itertools::Itertools;
238use proc_macro2::{Span, TokenStream};
239use quote::{ToTokens, TokenStreamExt, format_ident, quote};
240use syn::{
241 Attribute, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident,
242 parse_macro_input,
243};
244
245#[proc_macro_derive(JsonPointee, attributes(ploidy))]
249pub fn derive_pointee(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
250 let input = parse_macro_input!(input as DeriveInput);
251 derive_for(&input)
252 .unwrap_or_else(|err| err.to_compile_error())
253 .into()
254}
255
256fn derive_for(input: &DeriveInput) -> syn::Result<TokenStream> {
257 let name = &input.ident;
258 let attrs: Vec<_> = input
259 .attrs
260 .iter()
261 .map(ContainerAttr::parse_all)
262 .flatten_ok()
263 .try_collect()?;
264 let container =
265 ContainerInfo::new(name, &attrs).map_err(|err| syn::Error::new_spanned(input, err))?;
266
267 let pointer = Ident::new("pointer", Span::mixed_site());
269
270 let body = match &input.data {
271 Data::Struct(data) => {
272 if container.tag.is_some() {
273 return Err(syn::Error::new_spanned(input, DeriveError::TagOnNonEnum));
274 }
275 derive_for_struct(&pointer, container, data)?
276 }
277 Data::Enum(data) => derive_for_enum(&pointer, container, data)?,
278 Data::Union(_) => return Err(syn::Error::new_spanned(input, DeriveError::Union)),
279 };
280
281 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
282 let where_clause = {
283 let bounds = input
286 .generics
287 .params
288 .iter()
289 .filter_map(|param| match param {
290 GenericParam::Type(param) => {
291 let ident = ¶m.ident;
292 Some(quote! { #ident: ::ploidy_pointer::JsonPointee })
293 }
294 _ => None,
295 })
296 .collect_vec();
297 if bounds.is_empty() {
298 quote! { #where_clause }
299 } else if let Some(where_clause) = where_clause {
300 quote! { #where_clause #(#bounds),* }
301 } else {
302 quote! { where #(#bounds),* }
303 }
304 };
305
306 Ok(quote! {
307 #[automatically_derived]
308 impl #impl_generics ::ploidy_pointer::JsonPointee for #name #ty_generics #where_clause {
309 fn resolve(&self, #pointer: ::ploidy_pointer::JsonPointer<'_>)
310 -> ::std::result::Result<&dyn ::ploidy_pointer::JsonPointee, ::ploidy_pointer::BadJsonPointer> {
311 #body
312 }
313 }
314 })
315}
316
317fn derive_for_struct(
318 pointer: &Ident,
319 container: ContainerInfo<'_>,
320 data: &DataStruct,
321) -> syn::Result<TokenStream> {
322 let body = match &data.fields {
323 Fields::Named(fields) => {
324 let fields: Vec<_> = fields
325 .named
326 .iter()
327 .map(|f| NamedFieldInfo::new(container, f))
328 .try_collect()?;
329 let bindings = fields.iter().map(|f| {
330 let binding = f.binding;
331 quote! { #binding }
332 });
333 let body = NamedPointeeBody::new(NamedPointeeTy::Struct(container), pointer, &fields);
334 quote! {
335 let Self { #(#bindings),* } = self;
336 #body
337 }
338 }
339 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
340 quote! {
342 <_ as ::ploidy_pointer::JsonPointee>::resolve(&self.0, #pointer)
343 }
344 }
345 Fields::Unnamed(fields) => {
346 let fields: Vec<_> = fields
347 .unnamed
348 .iter()
349 .enumerate()
350 .map(|(index, f)| TupleFieldInfo::new(index, f))
351 .try_collect()?;
352 let bindings = fields.iter().map(|f| {
353 let binding = &f.binding;
354 quote! { #binding }
355 });
356 let body = TuplePointeeBody::new(TuplePointeeTy::Struct(container), pointer, &fields);
357 quote! {
358 let Self(#(#bindings),*) = self;
359 #body
360 }
361 }
362 Fields::Unit => {
363 let body = UnitPointeeBody::new(UnitPointeeTy::Struct(container), pointer);
364 quote!(#body)
365 }
366 };
367 Ok(body)
368}
369
370fn derive_for_enum(
371 pointer: &Ident,
372 container: ContainerInfo<'_>,
373 data: &DataEnum,
374) -> syn::Result<TokenStream> {
375 let tag = container.tag.unwrap_or(VariantTag::External);
378
379 let arms: Vec<_> = data
380 .variants
381 .iter()
382 .map(|variant| {
383 let name = &variant.ident;
384 let attrs: Vec<_> = variant
385 .attrs
386 .iter()
387 .map(VariantAttr::parse_all)
388 .flatten_ok()
389 .try_collect()?;
390 let info = VariantInfo::new(container, name, &attrs);
391
392 if info.is_skipped() {
395 let ty = match &variant.fields {
396 Fields::Named(_) => VariantTy::Named(info, tag),
397 Fields::Unnamed(_) => VariantTy::Tuple(info, tag),
398 Fields::Unit => VariantTy::Unit(info, tag),
399 };
400 let body = SkippedVariantBody::new(ty, pointer);
401 return syn::Result::Ok(quote!(#body));
402 }
403
404 let arm = match &variant.fields {
405 Fields::Named(fields) => {
406 let fields: Vec<_> = fields
407 .named
408 .iter()
409 .map(|f| NamedFieldInfo::new(container, f))
410 .try_collect()?;
411 let bindings = fields.iter().map(|f| {
412 let binding = f.binding;
413 quote! { #binding }
414 });
415 let body = NamedPointeeBody::new(
416 NamedPointeeTy::Variant(info, tag),
417 pointer,
418 &fields,
419 );
420 quote! {
421 Self::#name { #(#bindings),* } => {
422 #body
423 }
424 }
425 }
426 Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
427 match tag {
428 VariantTag::Internal(tag_field) => {
429 let key = Ident::new("key", Span::mixed_site());
432 let effective_name = info.effective_name();
433 quote! {
434 Self::#name(inner) => {
435 let Some(#key) = #pointer.head() else {
436 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
437 };
438 if #key.as_str() == #tag_field {
439 return Ok(&#effective_name as &dyn ::ploidy_pointer::JsonPointee);
440 }
441 <_ as ::ploidy_pointer::JsonPointee>::resolve(inner, #pointer)
442 }
443 }
444 }
445 VariantTag::External => {
446 let key = Ident::new("key", Span::mixed_site());
450 let effective_name = info.effective_name();
451 let pointee_ty = TuplePointeeTy::Variant(info, tag);
452 let key_err = if cfg!(feature = "did-you-mean") {
453 quote!(::ploidy_pointer::BadJsonPointerKey::with_ty(#key, #pointee_ty))
454 } else {
455 quote!(::ploidy_pointer::BadJsonPointerKey::new(#key))
456 };
457 quote! {
458 Self::#name(inner) => {
459 let Some(#key) = #pointer.head() else {
460 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
461 };
462 if #key.as_str() != #effective_name {
463 return Err(#key_err)?;
464 }
465 <_ as ::ploidy_pointer::JsonPointee>::resolve(inner, #pointer.tail())
466 }
467 }
468 }
469 VariantTag::Adjacent { tag: tag_field, content: content_field } => {
470 let key = Ident::new("key", Span::mixed_site());
473 let effective_name = info.effective_name();
474 let pointee_ty = TuplePointeeTy::Variant(info, tag);
475 let key_err = if cfg!(feature = "did-you-mean") {
476 quote!(::ploidy_pointer::BadJsonPointerKey::with_suggestions(
477 #key,
478 #pointee_ty,
479 [#tag_field, #content_field],
480 ))
481 } else {
482 quote!(::ploidy_pointer::BadJsonPointerKey::new(#key))
483 };
484 quote! {
485 Self::#name(inner) => {
486 let Some(#key) = #pointer.head() else {
487 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
488 };
489 match #key.as_str() {
490 #tag_field => Ok(&#effective_name as &dyn ::ploidy_pointer::JsonPointee),
491 #content_field => <_ as ::ploidy_pointer::JsonPointee>::resolve(inner, #pointer.tail()),
492 _ => Err(#key_err)?,
493 }
494 }
495 }
496 }
497 VariantTag::Untagged => {
498 quote! {
501 Self::#name(inner) => {
502 <_ as ::ploidy_pointer::JsonPointee>::resolve(
503 inner,
504 #pointer,
505 )
506 }
507 }
508 }
509 }
510 }
511 Fields::Unnamed(fields) => {
512 let fields: Vec<_> = fields
513 .unnamed
514 .iter()
515 .enumerate()
516 .map(|(index, f)| TupleFieldInfo::new(index, f))
517 .try_collect()?;
518 let bindings = fields.iter().map(|f| {
519 let binding = &f.binding;
520 quote! { #binding }
521 });
522 let body = TuplePointeeBody::new(
523 TuplePointeeTy::Variant(info, tag),
524 pointer,
525 &fields,
526 );
527 quote! {
528 Self::#name(#(#bindings),*) => {
529 #body
530 }
531 }
532 }
533 Fields::Unit => {
534 let body = UnitPointeeBody::new(
535 UnitPointeeTy::Variant(info, tag),
536 pointer,
537 );
538 quote! {
539 Self::#name => {
540 #body
541 }
542 }
543 }
544 };
545 syn::Result::Ok(arm)
546 })
547 .try_collect()?;
548
549 Ok(quote! {
550 match self {
551 #(#arms,)*
552 }
553 })
554}
555
556#[derive(Clone, Copy, Debug)]
557struct ContainerInfo<'a> {
558 name: &'a Ident,
559 rename_all: Option<RenameAll>,
560 tag: Option<VariantTag<'a>>,
561}
562
563impl<'a> ContainerInfo<'a> {
564 fn new(name: &'a Ident, attrs: &'a [ContainerAttr]) -> Result<Self, DeriveError> {
565 let rename_all = attrs.iter().find_map(|attr| match attr {
566 &ContainerAttr::RenameAll(rename_all) => Some(rename_all),
567 _ => None,
568 });
569
570 let tag = attrs
571 .iter()
572 .filter_map(|attr| match attr {
573 ContainerAttr::Tag(t) => Some(t.as_str()),
574 _ => None,
575 })
576 .at_most_one()
577 .map_err(|_| DeriveError::ConflictingTagAttributes)?;
578 let content = attrs
579 .iter()
580 .filter_map(|attr| match attr {
581 ContainerAttr::Content(c) => Some(c.as_str()),
582 _ => None,
583 })
584 .at_most_one()
585 .map_err(|_| DeriveError::ConflictingTagAttributes)?;
586 let untagged = attrs
587 .iter()
588 .filter(|attr| matches!(attr, ContainerAttr::Untagged))
589 .at_most_one()
590 .map_err(|_| DeriveError::ConflictingTagAttributes)?;
591 let tag = match (tag, content, untagged) {
592 (None, None, None) => None,
594 (Some(tag), None, None) => Some(VariantTag::Internal(tag)),
596 (None, None, Some(_)) => Some(VariantTag::Untagged),
598 (Some(tag), Some(content), None) if tag == content => {
599 return Err(DeriveError::SameTagAndContent);
600 }
601 (Some(tag), Some(content), None) => Some(VariantTag::Adjacent { tag, content }),
603 (None, Some(_), _) => return Err(DeriveError::ContentWithoutTag),
604 _ => return Err(DeriveError::ConflictingTagAttributes),
605 };
606
607 Ok(Self {
608 name,
609 rename_all,
610 tag,
611 })
612 }
613}
614
615#[derive(Debug)]
616struct NamedFieldInfo<'a> {
617 binding: &'a Ident,
618 key: String,
619 is_flattened: bool,
620 is_skipped: bool,
621}
622
623impl<'a> NamedFieldInfo<'a> {
624 fn new(container: ContainerInfo<'a>, f: &'a Field) -> syn::Result<Self> {
625 let name = f.ident.as_ref().unwrap();
626 let attrs: Vec<_> = f
627 .attrs
628 .iter()
629 .map(FieldAttr::parse_all)
630 .flatten_ok()
631 .try_collect()?;
632
633 let is_flattened = attrs.iter().any(|attr| matches!(attr, FieldAttr::Flatten));
634 let is_skipped = attrs.iter().any(|attr| matches!(attr, FieldAttr::Skip));
635
636 if is_flattened && is_skipped {
637 return Err(syn::Error::new_spanned(f, DeriveError::FlattenWithSkip));
638 }
639
640 let key = attrs
641 .iter()
642 .find_map(|attr| match attr {
643 FieldAttr::Rename(name) => Some(name.clone()),
644 _ => None,
645 })
646 .or_else(|| {
647 container
648 .rename_all
649 .map(|rename_all| rename_all.apply(&name.to_string()))
650 })
651 .unwrap_or_else(|| name.to_string());
652
653 Ok(NamedFieldInfo {
654 binding: name,
655 key,
656 is_flattened,
657 is_skipped,
658 })
659 }
660}
661
662#[derive(Debug)]
663struct TupleFieldInfo {
664 index: usize,
665 binding: Ident,
666 is_skipped: bool,
667}
668
669impl TupleFieldInfo {
670 fn new(index: usize, f: &Field) -> syn::Result<Self> {
671 let attrs: Vec<_> = f
672 .attrs
673 .iter()
674 .map(FieldAttr::parse_all)
675 .flatten_ok()
676 .try_collect()?;
677
678 let _: () = attrs
679 .iter()
680 .map(|attr| match attr {
681 FieldAttr::Flatten => {
682 Err(syn::Error::new_spanned(f, DeriveError::FlattenOnNonNamed))
683 }
684 FieldAttr::Rename(_) => {
685 Err(syn::Error::new_spanned(f, DeriveError::RenameOnNonNamed))
686 }
687 _ => Ok(()),
688 })
689 .try_collect()?;
690
691 let is_skipped = attrs.iter().any(|attr| matches!(attr, FieldAttr::Skip));
692
693 Ok(Self {
694 index,
695 binding: format_ident!("f{}", index, span = Span::mixed_site()),
696 is_skipped,
697 })
698 }
699}
700
701#[derive(Clone, Copy, Debug)]
702struct VariantInfo<'a> {
703 container: ContainerInfo<'a>,
704 name: &'a Ident,
705 attrs: &'a [VariantAttr],
706}
707
708impl<'a> VariantInfo<'a> {
709 fn new(container: ContainerInfo<'a>, name: &'a Ident, attrs: &'a [VariantAttr]) -> Self {
710 Self {
711 container,
712 name,
713 attrs,
714 }
715 }
716
717 fn effective_name(&self) -> String {
718 self.attrs
719 .iter()
720 .find_map(|attr| match attr {
721 VariantAttr::Rename(name) => Some(name.clone()),
722 _ => None,
723 })
724 .or_else(|| {
725 self.container
726 .rename_all
727 .map(|rename_all| rename_all.apply(&self.name.to_string()))
728 })
729 .unwrap_or_else(|| self.name.to_string())
730 }
731
732 fn is_skipped(&self) -> bool {
733 self.attrs
734 .iter()
735 .any(|attr| matches!(attr, VariantAttr::Skip))
736 }
737}
738
739#[derive(Clone, Copy, Debug)]
740struct NamedPointeeBody<'a> {
741 ty: NamedPointeeTy<'a>,
742 pointer: &'a Ident,
743 fields: &'a [NamedFieldInfo<'a>],
744}
745
746impl<'a> NamedPointeeBody<'a> {
747 fn new(ty: NamedPointeeTy<'a>, pointer: &'a Ident, fields: &'a [NamedFieldInfo]) -> Self {
748 Self {
749 ty,
750 pointer,
751 fields,
752 }
753 }
754}
755
756impl ToTokens for NamedPointeeBody<'_> {
757 fn to_tokens(&self, tokens: &mut TokenStream) {
758 let pointer = self.pointer;
759 let key = Ident::new("key", Span::mixed_site());
760 let pointee_ty = self.ty;
761
762 let arms = self
764 .fields
765 .iter()
766 .filter(|f| !f.is_flattened && !f.is_skipped)
767 .map(|f| {
768 let field_key = &f.key;
769 let binding = f.binding;
770 quote! {
771 #field_key => <_ as ::ploidy_pointer::JsonPointee>::resolve(
772 #binding,
773 #pointer.tail(),
774 )
775 }
776 });
777
778 let mut suggestions: Vec<_> = self
780 .fields
781 .iter()
782 .filter(|f| !f.is_flattened && !f.is_skipped)
783 .map(|f| {
784 let key = &f.key;
785 quote! { #key }
786 })
787 .collect();
788 if let NamedPointeeTy::Variant(_, VariantTag::Internal(tag)) = self.ty {
789 suggestions.push(quote! { #tag });
790 }
791
792 let wildcard = {
793 let rest = if cfg!(feature = "did-you-mean") {
796 quote!(Err(::ploidy_pointer::BadJsonPointerKey::with_suggestions(
797 #key,
798 #pointee_ty,
799 [#(#suggestions),*],
800 ))?)
801 } else {
802 quote!(Err(::ploidy_pointer::BadJsonPointerKey::new(#key))?)
803 };
804 self.fields
805 .iter()
806 .filter(|f| f.is_flattened)
807 .rfold(rest, |rest, f| {
808 let binding = f.binding;
809 quote! {
810 <_ as ::ploidy_pointer::JsonPointee>
811 ::resolve(
812 #binding,
813 #pointer.clone()
814 )
815 .or_else(|_| #rest)
816 }
817 })
818 };
819
820 let body = match self.ty {
821 NamedPointeeTy::Variant(info, VariantTag::Internal(tag_field)) => {
822 let variant_name = info.effective_name();
825 quote! {
826 let Some(#key) = #pointer.head() else {
827 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
828 };
829 if #key.as_str() == #tag_field {
830 return Ok(&#variant_name as &dyn ::ploidy_pointer::JsonPointee);
831 }
832 match #key.as_str() {
833 #(#arms,)*
834 _ => #wildcard,
835 }
836 }
837 }
838 NamedPointeeTy::Variant(info, VariantTag::External) => {
839 let variant_name = info.effective_name();
843 let ty_err = if cfg!(feature = "did-you-mean") {
844 quote!(::ploidy_pointer::BadJsonPointerTy::with_ty(&#pointer, #pointee_ty))
845 } else {
846 quote!(::ploidy_pointer::BadJsonPointerTy::new(&#pointer))
847 };
848 quote! {
849 let Some(#key) = #pointer.head() else {
850 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
851 };
852 if #key.as_str() != #variant_name {
853 return Err(#ty_err)?;
854 }
855 let #pointer = #pointer.tail();
856 let Some(#key) = #pointer.head() else {
857 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
858 };
859 match #key.as_str() {
860 #(#arms,)*
861 _ => #wildcard,
862 }
863 }
864 }
865 NamedPointeeTy::Variant(
866 info,
867 VariantTag::Adjacent {
868 tag: tag_field,
869 content: content_field,
870 },
871 ) => {
872 let variant_name = info.effective_name();
875 let key_err = if cfg!(feature = "did-you-mean") {
876 quote!(::ploidy_pointer::BadJsonPointerKey::with_suggestions(
877 #key,
878 #pointee_ty,
879 [#tag_field, #content_field],
880 ))
881 } else {
882 quote!(::ploidy_pointer::BadJsonPointerKey::new(#key))
883 };
884 quote! {
885 let Some(#key) = #pointer.head() else {
886 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
887 };
888 match #key.as_str() {
889 #tag_field => {
890 return Ok(&#variant_name as &dyn ::ploidy_pointer::JsonPointee);
891 }
892 #content_field => {
893 let #pointer = #pointer.tail();
894 let Some(#key) = #pointer.head() else {
895 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
896 };
897 match #key.as_str() {
898 #(#arms,)*
899 _ => #wildcard,
900 }
901 }
902 _ => {
903 return Err(#key_err)?;
904 }
905 }
906 }
907 }
908 NamedPointeeTy::Struct(_) | NamedPointeeTy::Variant(_, VariantTag::Untagged) => {
909 quote! {
912 let Some(#key) = #pointer.head() else {
913 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
914 };
915 match #key.as_str() {
916 #(#arms,)*
917 _ => #wildcard,
918 }
919 }
920 }
921 };
922
923 tokens.append_all(body);
924 }
925}
926
927#[derive(Clone, Copy, Debug)]
928struct TuplePointeeBody<'a> {
929 ty: TuplePointeeTy<'a>,
930 pointer: &'a Ident,
931 fields: &'a [TupleFieldInfo],
932}
933
934impl<'a> TuplePointeeBody<'a> {
935 fn new(ty: TuplePointeeTy<'a>, pointer: &'a Ident, fields: &'a [TupleFieldInfo]) -> Self {
936 Self {
937 ty,
938 pointer,
939 fields,
940 }
941 }
942}
943
944impl ToTokens for TuplePointeeBody<'_> {
945 fn to_tokens(&self, tokens: &mut TokenStream) {
946 let pointer = self.pointer;
947 let idx = Ident::new("idx", Span::mixed_site());
948 let key = Ident::new("key", Span::mixed_site());
949
950 let arms = self.fields.iter().filter(|f| !f.is_skipped).map(|f| {
952 let index = f.index;
953 let binding = &f.binding;
954 quote! {
955 #index => <_ as ::ploidy_pointer::JsonPointee>::resolve(
956 #binding,
957 #pointer.tail(),
958 )
959 }
960 });
961
962 let ty = self.ty;
964 let len = self.fields.len();
965 let ty_err = if cfg!(feature = "did-you-mean") {
966 quote!(::ploidy_pointer::BadJsonPointerTy::with_ty(&#pointer, #ty))
967 } else {
968 quote!(::ploidy_pointer::BadJsonPointerTy::new(&#pointer))
969 };
970 let tail = quote! {
971 let Some(#idx) = #key.to_index() else {
972 return Err(#ty_err)?;
973 };
974 match #idx {
975 #(#arms,)*
976 _ => Err(::ploidy_pointer::BadJsonPointer::Index(#idx, 0..#len))
977 }
978 };
979
980 let body = match self.ty {
981 TuplePointeeTy::Variant(info, VariantTag::Internal(tag_field)) => {
982 let variant_name = info.effective_name();
985 quote! {
986 let Some(#key) = #pointer.head() else {
987 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
988 };
989 if #key.as_str() == #tag_field {
990 return Ok(&#variant_name as &dyn ::ploidy_pointer::JsonPointee);
991 }
992 #tail
993 }
994 }
995 TuplePointeeTy::Variant(info, VariantTag::External) => {
996 let variant_name = info.effective_name();
1000 let ty_err = if cfg!(feature = "did-you-mean") {
1001 quote!(::ploidy_pointer::BadJsonPointerTy::with_ty(&#pointer, #ty))
1002 } else {
1003 quote!(::ploidy_pointer::BadJsonPointerTy::new(&#pointer))
1004 };
1005 quote! {
1006 let Some(#key) = #pointer.head() else {
1007 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
1008 };
1009 if #key.as_str() != #variant_name {
1010 return Err(#ty_err)?;
1011 }
1012 let #pointer = #pointer.tail();
1013 let Some(#key) = #pointer.head() else {
1014 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
1015 };
1016 #tail
1017 }
1018 }
1019 TuplePointeeTy::Variant(
1020 info,
1021 VariantTag::Adjacent {
1022 tag: tag_field,
1023 content: content_field,
1024 },
1025 ) => {
1026 let variant_name = info.effective_name();
1029 let key_err = if cfg!(feature = "did-you-mean") {
1030 quote!(::ploidy_pointer::BadJsonPointerKey::with_suggestions(
1031 #key,
1032 #ty,
1033 [#tag_field, #content_field],
1034 ))
1035 } else {
1036 quote!(::ploidy_pointer::BadJsonPointerKey::new(#key))
1037 };
1038 quote! {
1039 let Some(#key) = #pointer.head() else {
1040 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
1041 };
1042 match #key.as_str() {
1043 #tag_field => {
1044 return Ok(&#variant_name as &dyn ::ploidy_pointer::JsonPointee);
1045 }
1046 #content_field => {
1047 let #pointer = #pointer.tail();
1048 let Some(#key) = #pointer.head() else {
1049 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
1050 };
1051 #tail
1052 }
1053 _ => {
1054 return Err(#key_err)?;
1055 }
1056 }
1057 }
1058 }
1059 TuplePointeeTy::Struct(_) | TuplePointeeTy::Variant(_, VariantTag::Untagged) => {
1060 quote! {
1063 let Some(#key) = #pointer.head() else {
1064 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
1065 };
1066 #tail
1067 }
1068 }
1069 };
1070
1071 tokens.append_all(body);
1072 }
1073}
1074
1075#[derive(Clone, Copy, Debug)]
1076struct UnitPointeeBody<'a> {
1077 ty: UnitPointeeTy<'a>,
1078 pointer: &'a Ident,
1079}
1080
1081impl<'a> UnitPointeeBody<'a> {
1082 fn new(ty: UnitPointeeTy<'a>, pointer: &'a Ident) -> Self {
1083 Self { ty, pointer }
1084 }
1085}
1086
1087impl ToTokens for UnitPointeeBody<'_> {
1088 fn to_tokens(&self, tokens: &mut TokenStream) {
1089 let pointer = self.pointer;
1090 let body = match self.ty {
1091 ty @ UnitPointeeTy::Variant(info, VariantTag::Internal(tag_field)) => {
1092 let key = Ident::new("key", Span::mixed_site());
1094 let variant_name = info.effective_name();
1095 let key_err = if cfg!(feature = "did-you-mean") {
1096 quote!(::ploidy_pointer::BadJsonPointerKey::with_suggestions(
1097 #key,
1098 #ty,
1099 [#tag_field],
1100 ))
1101 } else {
1102 quote!(::ploidy_pointer::BadJsonPointerKey::new(#key))
1103 };
1104 quote! {
1105 let Some(#key) = #pointer.head() else {
1106 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
1107 };
1108 if #key.as_str() == #tag_field {
1109 return Ok(&#variant_name as &dyn ::ploidy_pointer::JsonPointee);
1110 }
1111 Err(#key_err)?
1112 }
1113 }
1114 ty @ UnitPointeeTy::Variant(info, VariantTag::External) => {
1115 let key = Ident::new("key", Span::mixed_site());
1117 let variant_name = info.effective_name();
1118 let key_err = if cfg!(feature = "did-you-mean") {
1119 quote!(::ploidy_pointer::BadJsonPointerKey::with_ty(#key, #ty))
1120 } else {
1121 quote!(::ploidy_pointer::BadJsonPointerKey::new(#key))
1122 };
1123 let ty_err = if cfg!(feature = "did-you-mean") {
1124 quote!(::ploidy_pointer::BadJsonPointerTy::with_ty(&#pointer.tail(), #ty))
1125 } else {
1126 quote!(::ploidy_pointer::BadJsonPointerTy::new(&#pointer.tail()))
1127 };
1128 quote! {
1129 let Some(#key) = #pointer.head() else {
1130 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
1131 };
1132 if #key.as_str() != #variant_name {
1133 return Err(#key_err)?;
1134 }
1135 if !#pointer.tail().is_empty() {
1136 return Err(#ty_err)?;
1137 }
1138 Ok(self as &dyn ::ploidy_pointer::JsonPointee)
1139 }
1140 }
1141 ty @ UnitPointeeTy::Variant(info, VariantTag::Adjacent { tag: tag_field, .. }) => {
1142 let key = Ident::new("key", Span::mixed_site());
1144 let variant_name = info.effective_name();
1145 let key_err = if cfg!(feature = "did-you-mean") {
1146 quote!(::ploidy_pointer::BadJsonPointerKey::with_suggestions(
1147 #key,
1148 #ty,
1149 [#tag_field],
1150 ))
1151 } else {
1152 quote!(::ploidy_pointer::BadJsonPointerKey::new(#key))
1153 };
1154 quote! {
1155 let Some(#key) = #pointer.head() else {
1156 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
1157 };
1158 match #key.as_str() {
1159 #tag_field => {
1160 return Ok(&#variant_name as &dyn ::ploidy_pointer::JsonPointee);
1161 }
1162 _ => {
1163 return Err(#key_err)?;
1164 }
1165 }
1166 }
1167 }
1168 ty @ (UnitPointeeTy::Struct(_) | UnitPointeeTy::Variant(_, VariantTag::Untagged)) => {
1169 let ty_err = if cfg!(feature = "did-you-mean") {
1171 quote!(::ploidy_pointer::BadJsonPointerTy::with_ty(&#pointer, #ty))
1172 } else {
1173 quote!(::ploidy_pointer::BadJsonPointerTy::new(&#pointer))
1174 };
1175 quote! {
1176 if #pointer.is_empty() {
1177 Ok(self as &dyn ::ploidy_pointer::JsonPointee)
1178 } else {
1179 Err(#ty_err)?
1180 }
1181 }
1182 }
1183 };
1184 tokens.append_all(body);
1185 }
1186}
1187
1188#[derive(Clone, Copy, Debug)]
1189enum NamedPointeeTy<'a> {
1190 Struct(ContainerInfo<'a>),
1191 Variant(VariantInfo<'a>, VariantTag<'a>),
1192}
1193
1194impl ToTokens for NamedPointeeTy<'_> {
1195 fn to_tokens(&self, tokens: &mut TokenStream) {
1196 tokens.append_all(match self {
1197 Self::Struct(info) => {
1198 let ty = info.name;
1199 quote! {
1200 ::ploidy_pointer::JsonPointeeTy::struct_named(
1201 stringify!(#ty)
1202 )
1203 }
1204 }
1205 Self::Variant(info, ..) => {
1206 let ty = info.container.name;
1207 let variant = info.name;
1208 quote! {
1209 ::ploidy_pointer::JsonPointeeTy::struct_variant_named(
1210 stringify!(#ty),
1211 stringify!(#variant),
1212 )
1213 }
1214 }
1215 });
1216 }
1217}
1218
1219#[derive(Clone, Copy, Debug)]
1220enum TuplePointeeTy<'a> {
1221 Struct(ContainerInfo<'a>),
1222 Variant(VariantInfo<'a>, VariantTag<'a>),
1223}
1224
1225impl ToTokens for TuplePointeeTy<'_> {
1226 fn to_tokens(&self, tokens: &mut TokenStream) {
1227 tokens.append_all(match self {
1228 Self::Struct(info) => {
1229 let ty = info.name;
1230 quote! {
1231 ::ploidy_pointer::JsonPointeeTy::tuple_struct_named(
1232 stringify!(#ty)
1233 )
1234 }
1235 }
1236 Self::Variant(info, ..) => {
1237 let ty = info.container.name;
1238 let variant = info.name;
1239 quote! {
1240 ::ploidy_pointer::JsonPointeeTy::tuple_variant_named(
1241 stringify!(#ty),
1242 stringify!(#variant),
1243 )
1244 }
1245 }
1246 });
1247 }
1248}
1249
1250#[derive(Clone, Copy, Debug)]
1251enum UnitPointeeTy<'a> {
1252 Struct(ContainerInfo<'a>),
1253 Variant(VariantInfo<'a>, VariantTag<'a>),
1254}
1255
1256impl ToTokens for UnitPointeeTy<'_> {
1257 fn to_tokens(&self, tokens: &mut TokenStream) {
1258 tokens.append_all(match self {
1259 Self::Struct(info) => {
1260 let ty = info.name;
1261 quote! {
1262 ::ploidy_pointer::JsonPointeeTy::unit_struct_named(
1263 stringify!(#ty)
1264 )
1265 }
1266 }
1267 Self::Variant(info, ..) => {
1268 let ty = info.container.name;
1269 let variant = info.name;
1270 quote! {
1271 ::ploidy_pointer::JsonPointeeTy::unit_variant_named(
1272 stringify!(#ty),
1273 stringify!(#variant),
1274 )
1275 }
1276 }
1277 });
1278 }
1279}
1280
1281#[derive(Clone, Copy, Debug)]
1282enum VariantTy<'a> {
1283 Named(VariantInfo<'a>, VariantTag<'a>),
1284 Tuple(VariantInfo<'a>, VariantTag<'a>),
1285 Unit(VariantInfo<'a>, VariantTag<'a>),
1286}
1287
1288impl<'a> VariantTy<'a> {
1289 fn info(self) -> VariantInfo<'a> {
1290 let (Self::Named(info, _) | Self::Tuple(info, _) | Self::Unit(info, _)) = self;
1291 info
1292 }
1293
1294 fn tag(self) -> VariantTag<'a> {
1295 let (Self::Named(_, tag) | Self::Tuple(_, tag) | Self::Unit(_, tag)) = self;
1296 tag
1297 }
1298}
1299
1300impl ToTokens for VariantTy<'_> {
1301 fn to_tokens(&self, tokens: &mut TokenStream) {
1302 tokens.append_all(match self {
1303 Self::Named(info, _) => {
1304 let ty = info.container.name;
1305 let variant = info.name;
1306 quote! {
1307 ::ploidy_pointer::JsonPointeeTy::struct_variant_named(
1308 stringify!(#ty),
1309 stringify!(#variant),
1310 )
1311 }
1312 }
1313 Self::Tuple(info, _) => {
1314 let ty = info.container.name;
1315 let variant = info.name;
1316 quote! {
1317 ::ploidy_pointer::JsonPointeeTy::tuple_variant_named(
1318 stringify!(#ty),
1319 stringify!(#variant),
1320 )
1321 }
1322 }
1323 Self::Unit(info, _) => {
1324 let ty = info.container.name;
1325 let variant = info.name;
1326 quote! {
1327 ::ploidy_pointer::JsonPointeeTy::unit_variant_named(
1328 stringify!(#ty),
1329 stringify!(#variant),
1330 )
1331 }
1332 }
1333 });
1334 }
1335}
1336
1337#[derive(Clone, Copy, Debug)]
1338struct SkippedVariantBody<'a> {
1339 ty: VariantTy<'a>,
1340 pointer: &'a Ident,
1341}
1342
1343impl<'a> SkippedVariantBody<'a> {
1344 fn new(ty: VariantTy<'a>, pointer: &'a Ident) -> Self {
1345 Self { ty, pointer }
1346 }
1347}
1348
1349impl ToTokens for SkippedVariantBody<'_> {
1350 fn to_tokens(&self, tokens: &mut TokenStream) {
1351 let pointer = self.pointer;
1352 let ty = self.ty;
1353
1354 let pattern = match ty {
1355 VariantTy::Named(info, _) => {
1356 let variant_name = info.name;
1357 quote!(Self::#variant_name { .. })
1358 }
1359 VariantTy::Tuple(info, _) => {
1360 let variant_name = info.name;
1361 quote!(Self::#variant_name(..))
1362 }
1363 VariantTy::Unit(info, _) => {
1364 let variant_name = info.name;
1365 quote!(Self::#variant_name)
1366 }
1367 };
1368
1369 match ty.tag() {
1370 VariantTag::Internal(tag_field) => {
1371 let key = Ident::new("key", Span::mixed_site());
1373 let effective_name = ty.info().effective_name();
1374 let ty_err = if cfg!(feature = "did-you-mean") {
1375 quote!(::ploidy_pointer::BadJsonPointerTy::with_ty(&#pointer, #ty))
1376 } else {
1377 quote!(::ploidy_pointer::BadJsonPointerTy::new(&#pointer))
1378 };
1379 tokens.append_all(quote! {
1380 #pattern => {
1381 let Some(#key) = #pointer.head() else {
1382 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
1383 };
1384 if #key.as_str() == #tag_field {
1385 return Ok(&#effective_name as &dyn ::ploidy_pointer::JsonPointee);
1386 }
1387 Err(#ty_err)?
1388 }
1389 });
1390 }
1391 VariantTag::External => {
1392 let ty_err = if cfg!(feature = "did-you-mean") {
1394 quote!(::ploidy_pointer::BadJsonPointerTy::with_ty(&#pointer, #ty))
1395 } else {
1396 quote!(::ploidy_pointer::BadJsonPointerTy::new(&#pointer))
1397 };
1398 tokens.append_all(quote! {
1399 #pattern => Err(#ty_err)?
1400 });
1401 }
1402 VariantTag::Adjacent { tag: tag_field, .. } => {
1403 let key = Ident::new("key", Span::mixed_site());
1406 let effective_name = ty.info().effective_name();
1407 let key_err = if cfg!(feature = "did-you-mean") {
1408 quote!(::ploidy_pointer::BadJsonPointerKey::with_suggestions(
1409 #key,
1410 #ty,
1411 [#tag_field],
1412 ))
1413 } else {
1414 quote!(::ploidy_pointer::BadJsonPointerKey::new(#key))
1415 };
1416 tokens.append_all(quote! {
1417 #pattern => {
1418 let Some(#key) = #pointer.head() else {
1419 return Ok(self as &dyn ::ploidy_pointer::JsonPointee);
1420 };
1421 match #key.as_str() {
1422 #tag_field => {
1423 return Ok(&#effective_name as &dyn ::ploidy_pointer::JsonPointee);
1424 }
1425 _ => {
1426 return Err(#key_err)?;
1427 }
1428 }
1429 }
1430 });
1431 }
1432 VariantTag::Untagged => {
1433 let ty_err = if cfg!(feature = "did-you-mean") {
1435 quote!(::ploidy_pointer::BadJsonPointerTy::with_ty(&#pointer, #ty))
1436 } else {
1437 quote!(::ploidy_pointer::BadJsonPointerTy::new(&#pointer))
1438 };
1439 tokens.append_all(quote! {
1440 #pattern => Err(#ty_err)?
1441 });
1442 }
1443 }
1444 }
1445}
1446
1447#[derive(Clone, Copy, Debug)]
1448enum VariantTag<'a> {
1449 Internal(&'a str),
1450 External,
1451 Adjacent { tag: &'a str, content: &'a str },
1452 Untagged,
1453}
1454
1455#[derive(Clone, Debug)]
1456enum ContainerAttr {
1457 RenameAll(RenameAll),
1458 Tag(String),
1459 Content(String),
1460 Untagged,
1461}
1462
1463impl ContainerAttr {
1464 fn parse_all(attr: &Attribute) -> syn::Result<Vec<Self>> {
1465 if !attr.path().is_ident("ploidy") {
1466 return Ok(vec![]);
1467 }
1468 let mut attrs = vec![];
1469 attr.parse_nested_meta(|meta| {
1470 if meta.path.is_ident("rename_all") {
1471 let value = meta.value()?;
1472 let s: syn::LitStr = value.parse()?;
1473 let Some(rename) = RenameAll::from_str(&s.value()) else {
1474 return Err(meta.error(DeriveError::BadRenameAll));
1475 };
1476 attrs.push(Self::RenameAll(rename));
1477 } else if meta.path.is_ident("tag") {
1478 let value = meta.value()?;
1479 let s: syn::LitStr = value.parse()?;
1480 attrs.push(Self::Tag(s.value()));
1481 } else if meta.path.is_ident("content") {
1482 let value = meta.value()?;
1483 let s: syn::LitStr = value.parse()?;
1484 attrs.push(Self::Content(s.value()));
1485 } else if meta.path.is_ident("untagged") {
1486 attrs.push(Self::Untagged);
1487 }
1488 Ok(())
1489 })?;
1490 Ok(attrs)
1491 }
1492}
1493
1494#[derive(Clone, Debug)]
1495enum FieldAttr {
1496 Rename(String),
1497 Flatten,
1498 Skip,
1499}
1500
1501impl FieldAttr {
1502 fn parse_all(attr: &Attribute) -> syn::Result<Vec<Self>> {
1503 if !attr.path().is_ident("ploidy") {
1504 return Ok(vec![]);
1505 }
1506 let mut attrs = vec![];
1507 attr.parse_nested_meta(|meta| {
1508 if meta.path.is_ident("rename") {
1509 let value = meta.value()?;
1510 let s: syn::LitStr = value.parse()?;
1511 attrs.push(Self::Rename(s.value()));
1512 } else if meta.path.is_ident("flatten") {
1513 attrs.push(Self::Flatten);
1514 } else if meta.path.is_ident("skip") {
1515 attrs.push(Self::Skip);
1516 }
1517 Ok(())
1518 })?;
1519 Ok(attrs)
1520 }
1521}
1522
1523#[derive(Clone, Debug)]
1524enum VariantAttr {
1525 Skip,
1526 Rename(String),
1527}
1528
1529impl VariantAttr {
1530 fn parse_all(attr: &Attribute) -> syn::Result<Vec<Self>> {
1531 if !attr.path().is_ident("ploidy") {
1532 return Ok(vec![]);
1533 }
1534 let mut attrs = vec![];
1535 attr.parse_nested_meta(|meta| {
1536 if meta.path.is_ident("skip") {
1537 attrs.push(Self::Skip);
1538 } else if meta.path.is_ident("rename") {
1539 let value = meta.value()?;
1540 let s: syn::LitStr = value.parse()?;
1541 attrs.push(Self::Rename(s.value()));
1542 }
1543 Ok(())
1544 })?;
1545 Ok(attrs)
1546 }
1547}
1548
1549#[derive(Clone, Copy, Debug)]
1551enum RenameAll {
1552 Lowercase,
1553 Uppercase,
1554 PascalCase,
1555 CamelCase,
1556 SnakeCase,
1557 ScreamingSnakeCase,
1558 KebabCase,
1559 ScreamingKebabCase,
1560}
1561
1562impl RenameAll {
1563 const fn all() -> &'static [Self] {
1564 &[
1565 Self::Lowercase,
1566 Self::Uppercase,
1567 Self::PascalCase,
1568 Self::CamelCase,
1569 Self::SnakeCase,
1570 Self::ScreamingSnakeCase,
1571 Self::KebabCase,
1572 Self::ScreamingKebabCase,
1573 ]
1574 }
1575
1576 fn from_str(s: &str) -> Option<Self> {
1577 Some(match s {
1578 "lowercase" => RenameAll::Lowercase,
1579 "UPPERCASE" => RenameAll::Uppercase,
1580 "PascalCase" => RenameAll::PascalCase,
1581 "camelCase" => RenameAll::CamelCase,
1582 "snake_case" => RenameAll::SnakeCase,
1583 "SCREAMING_SNAKE_CASE" => RenameAll::ScreamingSnakeCase,
1584 "kebab-case" => RenameAll::KebabCase,
1585 "SCREAMING-KEBAB-CASE" => RenameAll::ScreamingKebabCase,
1586 _ => return None,
1587 })
1588 }
1589
1590 fn apply(&self, s: &str) -> String {
1591 match self {
1592 RenameAll::Lowercase => s.to_lowercase(),
1593 RenameAll::Uppercase => s.to_uppercase(),
1594 RenameAll::PascalCase => s.to_pascal_case(),
1595 RenameAll::CamelCase => s.to_lower_camel_case(),
1596 RenameAll::SnakeCase => s.to_snake_case(),
1597 RenameAll::ScreamingSnakeCase => s.to_shouty_snake_case(),
1598 RenameAll::KebabCase => s.to_kebab_case(),
1599 RenameAll::ScreamingKebabCase => s.to_shouty_kebab_case(),
1600 }
1601 }
1602}
1603
1604impl Display for RenameAll {
1605 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1606 f.write_str(match self {
1607 Self::Lowercase => "lowercase",
1608 Self::Uppercase => "UPPERCASE",
1609 Self::PascalCase => "PascalCase",
1610 Self::CamelCase => "camelCase",
1611 Self::SnakeCase => "snake_case",
1612 Self::ScreamingSnakeCase => "SCREAMING_SNAKE_CASE",
1613 Self::KebabCase => "kebab-case",
1614 Self::ScreamingKebabCase => "SCREAMING-KEBAB-CASE",
1615 })
1616 }
1617}
1618
1619#[derive(Debug, thiserror::Error)]
1620enum DeriveError {
1621 #[error("`JsonPointee` can't be derived for unions")]
1622 Union,
1623 #[error("`rename` is only supported on struct and struct-like enum variant fields")]
1624 RenameOnNonNamed,
1625 #[error("`flatten` is only supported on struct and struct-like enum variant fields")]
1626 FlattenOnNonNamed,
1627 #[error("`flatten` and `skip` are mutually exclusive")]
1628 FlattenWithSkip,
1629 #[error("`tag` is only supported on enums")]
1630 TagOnNonEnum,
1631 #[error("`content` requires `tag`")]
1632 ContentWithoutTag,
1633 #[error("`tag` and `content` must have different field names")]
1634 SameTagAndContent,
1635 #[error("only one of: `tag`, `tag` and `content`, `untagged` allowed")]
1636 ConflictingTagAttributes,
1637 #[error("`rename_all` must be one of: {}", RenameAll::all().iter().join(","))]
1638 BadRenameAll,
1639}