ploidy_pointer_derive/
lib.rs

1//! Derive macro for the [`JsonPointee`] trait.
2//!
3//! This crate provides a derive macro to generate a [`JsonPointee`]
4//! implementation for a Rust data structure. The macro can generate implementations for
5//! structs and enums, and supports [Serde][serde]-like attributes.
6//!
7//! # Container attributes
8//!
9//! Container-level attributes apply to structs and enums:
10//!
11//! * `#[ploidy(tag = "field")]` - Use the internally tagged enum representation,
12//!   with the given field name for the tag. Supported on enums only.
13//! * `#[ploidy(tag = "t", content = "c")]` - Use the adjacently tagged enum representation,
14//!   with the given field names for the tag and contents. Supported on enums only.
15//! * `#[ploidy(untagged)]` - Use the untagged enum representation. Supported on enums only.
16//! * `#[ploidy(rename_all = "case")]` - Rename all struct fields or enum variants
17//!   according to the given case. The supported cases are `lowercase`, `UPPERCASE`,
18//!   `PascalCase`, `camelCase`, `snake_case`, `SCREAMING_SNAKE_CASE`, `kebab-case`, and
19//!   `SCREAMING-KEBAB-CASE`.
20//!
21//! # Variant Attributes
22//!
23//! Variant-level attributes apply to enum variants:
24//!
25//! * `#[ploidy(rename = "name")]` - Access this variant using the given name,
26//!   instead of its Rust name.
27//! * `#[ploidy(skip)]` - Make this variant inaccessible, except for the tag field
28//!   if using the internally or adjacently tagged enum representation.
29//!
30//! # Field Attributes
31//!
32//! Field-level attributes apply to struct and enum variant fields:
33//!
34//! * `#[ploidy(rename = "name")]` - Access this variant using the given name,
35//!   instead of its Rust name.
36//! * `#[ploidy(flatten)]` - Remove one layer of structure between the container
37//!   and field. Supported on named fields only.
38//! * `#[ploidy(skip)]` - Exclude the field from pointer access.
39//!
40//! # Examples
41//!
42//! ## Struct flattening
43//!
44//! ```ignore
45//! # use ploidy_pointer::{BadJsonPointer, JsonPointee, JsonPointer};
46//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
47//! #[derive(JsonPointee)]
48//! struct User {
49//!     name: String,
50//!     #[ploidy(flatten)]
51//!     contact: ContactInfo,
52//! }
53//!
54//! #[derive(JsonPointee)]
55//! struct ContactInfo {
56//!     email: String,
57//!     phone: String,
58//! }
59//!
60//! let user = User {
61//!     name: "Alice".to_owned(),
62//!     contact: ContactInfo {
63//!         email: "a@example.com".to_owned(),
64//!         phone: "555-1234".to_owned(),
65//!     },
66//! };
67//! assert_eq!(
68//!     user.resolve(JsonPointer::parse("/name")?)?.downcast_ref::<String>(),
69//!     Some(&"Alice".to_owned()),
70//! );
71//! assert_eq!(
72//!     user.resolve(JsonPointer::parse("/email")?)?.downcast_ref::<String>(),
73//!     Some(&"a@example.com".to_owned()),
74//! );
75//! assert_eq!(
76//!     user.resolve(JsonPointer::parse("/phone")?)?.downcast_ref::<String>(),
77//!     Some(&"555-1234".to_owned()),
78//! );
79//! # Ok(())
80//! # }
81//! ```
82//!
83//! ## Renaming fields
84//!
85//! ```ignore
86//! # use ploidy_pointer::{BadJsonPointer, JsonPointee, JsonPointer};
87//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
88//! #[derive(JsonPointee)]
89//! #[ploidy(rename_all = "snake_case")]
90//! enum ApiResponse {
91//!     SuccessResponse { data: String },
92//!     #[ploidy(rename = "error")]
93//!     ErrorResponse { message: String },
94//! }
95//!
96//! let success = ApiResponse::SuccessResponse {
97//!     data: "ok".to_owned(),
98//! };
99//! assert_eq!(
100//!     success.resolve(JsonPointer::parse("/success_response/data")?)?.downcast_ref::<String>(),
101//!     Some(&"ok".to_owned()),
102//! );
103//!
104//! let error = ApiResponse::ErrorResponse {
105//!     message: "failed".to_owned(),
106//! };
107//! assert_eq!(
108//!     error.resolve(JsonPointer::parse("/error/message")?)?.downcast_ref::<String>(),
109//!     Some(&"failed".to_owned()),
110//! );
111//! # Ok(())
112//! # }
113//! ```
114//!
115//! # Enum representations
116//!
117//! Like Serde, `#[derive(JsonPointee)]` supports externally tagged,
118//! internally tagged, adjacently tagged, and untagged enum representations.
119//!
120//! ## Externally tagged
121//!
122//! This is the default enum representation. The variant's tag wraps the contents.
123//!
124//! ```ignore
125//! # use ploidy_pointer::{BadJsonPointer, JsonPointee, JsonPointer};
126//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
127//! #[derive(JsonPointee)]
128//! enum Message {
129//!     Text { content: String },
130//!     Image { url: String },
131//! }
132//!
133//! let message = Message::Text {
134//!     content: "hello".to_owned(),
135//! };
136//! assert_eq!(
137//!     message.resolve(JsonPointer::parse("/Text/content")?)?.downcast_ref::<String>(),
138//!     Some(&"hello".to_owned()),
139//! );
140//! # Ok(())
141//! # }
142//! ```
143//!
144//! ## Internally tagged
145//!
146//! In this representation, the tag that specifies the variant name
147//! is next to the variant's fields.
148//!
149//! ```ignore
150//! # use ploidy_pointer::{BadJsonPointer, JsonPointee, JsonPointer};
151//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
152//! #[derive(JsonPointee)]
153//! #[ploidy(tag = "type")]
154//! enum Message {
155//!     Text { content: String },
156//!     Image { url: String },
157//! }
158//!
159//! let message = Message::Text {
160//!     content: "hello".to_owned(),
161//! };
162//! assert_eq!(
163//!     message.resolve(JsonPointer::parse("/type")?)?.downcast_ref::<&str>(),
164//!     Some(&"Text"),
165//! );
166//! assert_eq!(
167//!     message.resolve(JsonPointer::parse("/content")?)?.downcast_ref::<String>(),
168//!     Some(&"hello".to_owned()),
169//! );
170//! # Ok(())
171//! # }
172//! ```
173//!
174//! ## Adjacently tagged
175//!
176//! In this representation, the variant's tag and contents are adjacent
177//! to each other, as two fields in the same object.
178//!
179//! ```ignore
180//! # use ploidy_pointer::{BadJsonPointer, JsonPointee, JsonPointer};
181//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
182//! #[derive(JsonPointee)]
183//! #[ploidy(tag = "type", content = "value")]
184//! enum Message {
185//!     Text { content: String },
186//!     Image { url: String },
187//! }
188//!
189//! let message = Message::Text {
190//!     content: "hello".to_owned(),
191//! };
192//! assert_eq!(
193//!     message.resolve(JsonPointer::parse("/type")?)?.downcast_ref::<&str>(),
194//!     Some(&"Text"),
195//! );
196//! assert_eq!(
197//!     message.resolve(JsonPointer::parse("/value/content")?)?.downcast_ref::<String>(),
198//!     Some(&"hello".to_owned()),
199//! );
200//! # Ok(())
201//! # }
202//! ```
203//!
204//! ## Untagged
205//!
206//! In this representation, the variant's name is completely ignored,
207//! and pointers are resolved against the variant's contents.
208//!
209//! ```ignore
210//! # use ploidy_pointer::{BadJsonPointer, JsonPointee, JsonPointer};
211//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
212//! #[derive(JsonPointee)]
213//! #[ploidy(untagged)]
214//! enum Message {
215//!     Text { content: String },
216//!     Image { url: String },
217//! }
218//!
219//! let message = Message::Text {
220//!     content: "hello".to_owned(),
221//! };
222//! assert_eq!(
223//!     message.resolve(JsonPointer::parse("/content")?)?.downcast_ref::<String>(),
224//!     Some(&"hello".to_owned()),
225//! );
226//! # Ok(())
227//! # }
228//! ```
229//!
230//! [serde]: https://serde.rs
231
232use 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/// Derives the `JsonPointee` trait for JSON Pointer (RFC 6901) traversal.
246///
247/// See the [module documentation][crate] for detailed usage and examples.
248#[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    // Hygienic parameter for the generated `resolve` method.
268    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        // Add or extend the `where` clause with `T: JsonPointee` bounds
284        // for all generic type parameters.
285        let bounds = input
286            .generics
287            .params
288            .iter()
289            .filter_map(|param| match param {
290                GenericParam::Type(param) => {
291                    let ident = &param.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            // For newtype structs, resolve the pointer against the inner value.
341            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    // Default to the externally tagged representation
376    // if a tag isn't explicitly specified.
377    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            // For skipped variants, derive an implementation
393            // that always returns an error.
394            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                            // For internally tagged newtype variants, check the tag field
430                            // before delegating to the inner value.
431                            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                            // For externally tagged newtype variants, the first segment
447                            // must match the variant name; then the tail should resolve
448                            // against the inner value.
449                            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                            // For adjacently tagged newtype variants, the first segment
471                            // must match either the tag or content field.
472                            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                            // For untagged newtype variants, transparently resolve
499                            // against the inner value.
500                            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            // No explicit tag.
593            (None, None, None) => None,
594            // Internally tagged: only `tag`.
595            (Some(tag), None, None) => Some(VariantTag::Internal(tag)),
596            // Untagged: only `untagged`.
597            (None, None, Some(_)) => Some(VariantTag::Untagged),
598            (Some(tag), Some(content), None) if tag == content => {
599                return Err(DeriveError::SameTagAndContent);
600            }
601            // Adjacently tagged: both `tag` and `content`.
602            (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        // Build match arms for fields.
763        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        // Build field suggestions for error messages.
779        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            // For flattened fields, we build an `.or_else()` chain bottom-up
794            // using a right fold.
795            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                // For internally tagged struct-like variants, check the tag field
823                // before resolving against the named fields.
824                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                // For externally tagged struct-like variants, the first segment
840                // must match the variant name; then the tail should resolve
841                // against the named fields.
842                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                // For adjacently tagged struct-like variants, the first segment
873                // must match either the tag or content field.
874                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                // For structs and untagged struct-like variants,
910                // access the fields directly.
911                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        // Build match arms for tuple indices.
951        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        // Build common tail.
963        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                // For internally tagged tuple variants, check the tag field
983                // before resolving against the tuple indices.
984                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                // For externally tagged tuple variants, the first segment
997                // must match the variant name; then the tail should resolve
998                // against the tuple indices.
999                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                // For adjacently tagged tuple variants, the first segment
1027                // must match either the tag or content field.
1028                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                // For structs and untagged tuple variants,
1061                // access the tuple indices directly.
1062                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                // For internally tagged unit variants, only the tag field is accessible.
1093                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                // For externally tagged unit variants, allow just the tag field.
1116                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                // For adjacently tagged unit variants, allow just the tag field.
1143                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                // For unit structs and untagged unit variants, deny all fields.
1170                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                // Internally tagged skipped variants allow access to the tag field only.
1372                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                // Externally tagged skipped variants are completely inaccessible.
1393                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                // Adjacently tagged skipped variants allow tag field access,
1404                // but content field access errors.
1405                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                // Untagged skipped variants are completely inaccessible.
1434                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/// Supported `rename_all` transforms, matching Serde.
1550#[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}