Skip to main content

ploidy_pointer_derive/
lib.rs

1//! Derive macros for the [`JsonPointee`] and [`JsonPointerTarget`] traits.
2//!
3//! This crate provides two derive macros:
4//!
5//! * [`JsonPointee`] generates an implementation of the [`JsonPointee`][pointee]
6//!   trait for traversing structs and enums with JSON Pointer (RFC 6901)
7//!   syntax. This macro supports [Serde][serde]-like attributes.
8//! * [`JsonPointerTarget`] generates an implementation of the
9//!   [`JsonPointerTarget`][target] trait for extracting a reference to the
10//!   concrete target type from a type-erased pointee.
11//!
12//! # Container attributes
13//!
14//! Container-level attributes apply to structs and enums:
15//!
16//! * `#[ploidy(pointer(tag = "field"))]` - Use the internally tagged enum representation,
17//!   with the given field name for the tag. Supported on enums only.
18//! * `#[ploidy(pointer(tag = "t", content = "c"))]` - Use the adjacently tagged enum representation,
19//!   with the given field names for the tag and contents. Supported on enums only.
20//! * `#[ploidy(pointer(untagged))]` - Use the untagged enum representation. Supported on enums only.
21//! * `#[ploidy(pointer(rename_all = "case"))]` - Rename all struct fields or enum variants
22//!   according to the given case. The supported cases are `lowercase`, `UPPERCASE`,
23//!   `PascalCase`, `camelCase`, `snake_case`, `SCREAMING_SNAKE_CASE`, `kebab-case`, and
24//!   `SCREAMING-KEBAB-CASE`.
25//! * `#[ploidy(pointer(crate = "path::to::ploidy_pointer"))]` - Override the path to the
26//!   `ploidy_pointer` crate. Defaults to `::ploidy_pointer`.
27//!
28//! # Variant Attributes
29//!
30//! Variant-level attributes apply to enum variants:
31//!
32//! * `#[ploidy(pointer(rename = "name"))]` - Access this variant using the given name,
33//!   instead of its Rust name.
34//! * `#[ploidy(pointer(skip))]` - Make this variant inaccessible, except for the tag field
35//!   if using the internally or adjacently tagged enum representation.
36//!
37//! # Field Attributes
38//!
39//! Field-level attributes apply to struct and enum variant fields:
40//!
41//! * `#[ploidy(pointer(rename = "name"))]` - Access this variant using the given name,
42//!   instead of its Rust name.
43//! * `#[ploidy(pointer(flatten))]` - Remove one layer of structure between the container
44//!   and field. Supported on named fields only.
45//! * `#[ploidy(pointer(skip))]` - Exclude the field from pointer access.
46//!
47//! # Examples
48//!
49//! ## Struct flattening
50//!
51//! ```ignore
52//! # use ploidy_pointer::{JsonPointerTarget, JsonPointee, JsonPointeeExt};
53//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
54//! #[derive(JsonPointee, JsonPointerTarget)]
55//! struct User {
56//!     name: String,
57//!     #[ploidy(pointer(flatten))]
58//!     contact: ContactInfo,
59//! }
60//!
61//! #[derive(JsonPointee, JsonPointerTarget)]
62//! struct ContactInfo {
63//!     email: String,
64//!     phone: String,
65//! }
66//!
67//! let user = User {
68//!     name: "Alice".to_owned(),
69//!     contact: ContactInfo {
70//!         email: "a@example.com".to_owned(),
71//!         phone: "555-1234".to_owned(),
72//!     },
73//! };
74//! let name: &str = user.pointer("/name")?;
75//! assert_eq!(name, "Alice");
76//! let email: &str = user.pointer("/email")?;
77//! assert_eq!(email, "a@example.com");
78//! let phone: &str = user.pointer("/phone")?;
79//! assert_eq!(phone, "555-1234");
80//! # Ok(())
81//! # }
82//! ```
83//!
84//! ## Renaming fields
85//!
86//! ```ignore
87//! # use ploidy_pointer::{JsonPointerTarget, JsonPointee, JsonPointeeExt};
88//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
89//! #[derive(JsonPointee, JsonPointerTarget)]
90//! #[ploidy(pointer(rename_all = "snake_case"))]
91//! enum ApiResponse {
92//!     SuccessResponse { data: String },
93//!     #[ploidy(pointer(rename = "error"))]
94//!     ErrorResponse { message: String },
95//! }
96//!
97//! let success = ApiResponse::SuccessResponse {
98//!     data: "ok".to_owned(),
99//! };
100//! let data: &str = success.pointer("/success_response/data")?;
101//! assert_eq!(data, "ok");
102//!
103//! let error = ApiResponse::ErrorResponse {
104//!     message: "failed".to_owned(),
105//! };
106//! let message: &str = error.pointer("/error/message")?;
107//! assert_eq!(message, "failed");
108//! # Ok(())
109//! # }
110//! ```
111//!
112//! # Enum representations
113//!
114//! Like Serde, `#[derive(JsonPointee)]` supports externally tagged,
115//! internally tagged, adjacently tagged, and untagged enum representations.
116//!
117//! ## Externally tagged
118//!
119//! This is the default enum representation. The variant's tag wraps the contents.
120//!
121//! ```ignore
122//! # use ploidy_pointer::{JsonPointerTarget, JsonPointee, JsonPointeeExt};
123//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
124//! #[derive(JsonPointee, JsonPointerTarget)]
125//! enum Message {
126//!     Text { content: String },
127//!     Image { url: String },
128//! }
129//!
130//! let message = Message::Text {
131//!     content: "hello".to_owned(),
132//! };
133//! let content: &str = message.pointer("/Text/content")?;
134//! assert_eq!(content, "hello");
135//! # Ok(())
136//! # }
137//! ```
138//!
139//! ## Internally tagged
140//!
141//! In this representation, the tag that specifies the variant name
142//! is next to the variant's fields.
143//!
144//! ```ignore
145//! # use ploidy_pointer::{JsonPointerTarget, JsonPointee, JsonPointeeExt};
146//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
147//! #[derive(JsonPointee, JsonPointerTarget)]
148//! #[ploidy(pointer(tag = "type"))]
149//! enum Message {
150//!     Text { content: String },
151//!     Image { url: String },
152//! }
153//!
154//! let message = Message::Text {
155//!     content: "hello".to_owned(),
156//! };
157//! let tag: &str = message.pointer("/type")?;
158//! assert_eq!(tag, "Text");
159//! let content: &str = message.pointer("/content")?;
160//! assert_eq!(content, "hello");
161//! # Ok(())
162//! # }
163//! ```
164//!
165//! ## Adjacently tagged
166//!
167//! In this representation, the variant's tag and contents are adjacent
168//! to each other, as two fields in the same object.
169//!
170//! ```ignore
171//! # use ploidy_pointer::{JsonPointerTarget, JsonPointee, JsonPointeeExt};
172//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
173//! #[derive(JsonPointee, JsonPointerTarget)]
174//! #[ploidy(pointer(tag = "type", content = "value"))]
175//! enum Message {
176//!     Text { content: String },
177//!     Image { url: String },
178//! }
179//!
180//! let message = Message::Text {
181//!     content: "hello".to_owned(),
182//! };
183//! let tag: &str = message.pointer("/type")?;
184//! assert_eq!(tag, "Text");
185//! let content: &str = message.pointer("/value/content")?;
186//! assert_eq!(content, "hello");
187//! # Ok(())
188//! # }
189//! ```
190//!
191//! ## Untagged
192//!
193//! In this representation, the variant's name is completely ignored,
194//! and pointers are resolved against the variant's contents.
195//!
196//! ```ignore
197//! # use ploidy_pointer::{JsonPointerTarget, JsonPointee, JsonPointeeExt};
198//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
199//! #[derive(JsonPointee, JsonPointerTarget)]
200//! #[ploidy(pointer(untagged))]
201//! enum Message {
202//!     Text { content: String },
203//!     Image { url: String },
204//! }
205//!
206//! let message = Message::Text {
207//!     content: "hello".to_owned(),
208//! };
209//! let content: &str = message.pointer("/content")?;
210//! assert_eq!(content, "hello");
211//! # Ok(())
212//! # }
213//! ```
214//!
215//! [serde]: https://serde.rs
216//! [pointee]: https://docs.rs/ploidy-pointer/latest/ploidy_pointer/trait.JsonPointee.html
217//! [target]: https://docs.rs/ploidy-pointer/latest/ploidy_pointer/trait.JsonPointerTarget.html
218
219use std::{borrow::Cow, fmt::Display};
220
221use heck::{
222    ToKebabCase, ToLowerCamelCase, ToPascalCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase,
223};
224use itertools::Itertools;
225use proc_macro2::{Span, TokenStream};
226use quote::{ToTokens, TokenStreamExt, format_ident, quote};
227use syn::{
228    Attribute, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident,
229    Lifetime, parse_macro_input,
230};
231
232/// Derives the `JsonPointee` trait for JSON Pointer (RFC 6901) traversal.
233///
234/// See the [module documentation][crate] for detailed usage and examples.
235#[proc_macro_derive(JsonPointee, attributes(ploidy))]
236pub fn derive_pointee(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
237    let input = parse_macro_input!(input as DeriveInput);
238    derive_pointee_for(&input)
239        .unwrap_or_else(|err| err.to_compile_error())
240        .into()
241}
242
243/// Derives the `JsonPointerTarget` trait for downcasting a type-erased
244/// `JsonPointee` reference to a concrete type reference.
245///
246/// Note that this macro generates an `impl JsonPointerTarget<'a>` for `&'a T`,
247/// **not for** `T`.
248///
249/// The only supported container attribute is
250/// `#[ploidy(pointer(crate = "path"))]` to override the crate path.
251#[proc_macro_derive(JsonPointerTarget, attributes(ploidy))]
252pub fn derive_pointer_target(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
253    let input = parse_macro_input!(input as DeriveInput);
254    derive_pointer_target_for(&input)
255        .unwrap_or_else(|err| err.to_compile_error())
256        .into()
257}
258
259fn derive_pointee_for(input: &DeriveInput) -> syn::Result<TokenStream> {
260    let name = &input.ident;
261    let attrs = ContainerAttr::parse_all(&input.attrs)?;
262    let root = crate_path(&attrs);
263    let container = ContainerInfo::new(name, &root, &attrs)
264        .map_err(|err| syn::Error::new_spanned(input, err))?;
265
266    // Hygienic parameter for the generated `resolve` method.
267    let pointer = Ident::new("pointer", Span::mixed_site());
268
269    let body = match &input.data {
270        Data::Struct(data) => {
271            if container.tag.is_some() {
272                return Err(syn::Error::new_spanned(input, DeriveError::TagOnNonEnum));
273            }
274            derive_for_struct(&pointer, container, data)?
275        }
276        Data::Enum(data) => derive_for_enum(&pointer, container, data)?,
277        Data::Union(_) => return Err(syn::Error::new_spanned(input, DeriveError::Union)),
278    };
279
280    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
281    let where_clause = {
282        // Add or extend the `where` clause with `T: JsonPointee` bounds
283        // for all generic type parameters.
284        let type_param_bounds = input
285            .generics
286            .params
287            .iter()
288            .filter_map(|param| match param {
289                GenericParam::Type(param) => {
290                    let ident = &param.ident;
291                    Some(quote! { #ident: #root::JsonPointee })
292                }
293                _ => None,
294            })
295            .collect_vec();
296        if type_param_bounds.is_empty() {
297            quote! { #where_clause }
298        } else if let Some(where_clause) = where_clause {
299            quote! { #where_clause #(#type_param_bounds),* }
300        } else {
301            quote! { where #(#type_param_bounds),* }
302        }
303    };
304
305    Ok(quote! {
306        #[automatically_derived]
307        impl #impl_generics #root::JsonPointee for #name #ty_generics #where_clause {
308            fn resolve(&self, #pointer: &#root::JsonPointer)
309                -> ::std::result::Result<&dyn #root::JsonPointee, #root::JsonPointeeError> {
310                #body
311            }
312        }
313    })
314}
315
316fn derive_pointer_target_for(input: &DeriveInput) -> syn::Result<TokenStream> {
317    let name = &input.ident;
318    let attrs = ContainerAttr::parse_all(&input.attrs)?;
319    let root = crate_path(&attrs);
320
321    // Hygienic lifetime parameter for `impl JsonPointerTarget<'pointee>`.
322    let lifetime = Lifetime::new("'pointee", Span::mixed_site());
323
324    let (_, ty_generics, where_clause) = input.generics.split_for_impl();
325    let generics = {
326        let mut generics = input.generics.clone();
327        generics.params.push(syn::parse_quote!(#lifetime));
328        generics
329    };
330    let (impl_generics, _, _) = generics.split_for_impl();
331    let where_clause = match where_clause {
332        // `downcast_ref::<T>` requires `T: Any`.
333        Some(where_clause) => {
334            let s = quote! { #name #ty_generics: ::std::any::Any };
335            quote! { #where_clause, #s }
336        }
337        None => quote! { where #name #ty_generics: ::std::any::Any },
338    };
339
340    Ok(quote! {
341        #[automatically_derived]
342        impl #impl_generics #root::JsonPointerTarget<#lifetime>
343            for &#lifetime #name #ty_generics
344        #where_clause
345        {
346            #[inline]
347            fn from_pointee(
348                pointee: &#lifetime dyn #root::JsonPointee,
349            ) -> ::std::result::Result<Self, #root::JsonPointerTargetError> {
350                let any: &dyn ::std::any::Any = pointee;
351                any.downcast_ref().ok_or_else(|| #root::JsonPointerTargetError {
352                    expected: ::std::any::type_name::<#name #ty_generics>(),
353                    actual: pointee.name(),
354                })
355            }
356        }
357    })
358}
359
360fn derive_for_struct(
361    pointer: &Ident,
362    container: ContainerInfo<'_>,
363    data: &DataStruct,
364) -> syn::Result<TokenStream> {
365    let body = match &data.fields {
366        Fields::Named(fields) => {
367            let fields: Vec<_> = fields
368                .named
369                .iter()
370                .map(|f| NamedFieldInfo::new(container, f))
371                .try_collect()?;
372            let bindings = fields.iter().map(|f| {
373                let binding = f.binding;
374                quote! { #binding }
375            });
376            let body = NamedPointeeBody::new(NamedPointeeTy::Struct(container), pointer, &fields);
377            quote! {
378                let Self { #(#bindings),* } = self;
379                #body
380            }
381        }
382        Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
383            // For newtype structs, resolve the pointer against the inner value.
384            let root = container.root;
385            quote! {
386                <_ as #root::JsonPointee>::resolve(&self.0, #pointer)
387            }
388        }
389        Fields::Unnamed(fields) => {
390            let fields: Vec<_> = fields
391                .unnamed
392                .iter()
393                .enumerate()
394                .map(|(index, f)| TupleFieldInfo::new(index, f))
395                .try_collect()?;
396            let bindings = fields.iter().map(|f| {
397                let binding = &f.binding;
398                quote! { #binding }
399            });
400            let body = TuplePointeeBody::new(TuplePointeeTy::Struct(container), pointer, &fields);
401            quote! {
402                let Self(#(#bindings),*) = self;
403                #body
404            }
405        }
406        Fields::Unit => {
407            let body = UnitPointeeBody::new(UnitPointeeTy::Struct(container), pointer);
408            quote!(#body)
409        }
410    };
411    Ok(body)
412}
413
414fn derive_for_enum(
415    pointer: &Ident,
416    container: ContainerInfo<'_>,
417    data: &DataEnum,
418) -> syn::Result<TokenStream> {
419    // Default to the externally tagged representation
420    // if a tag isn't explicitly specified.
421    let tag = container.tag.unwrap_or(VariantTag::External);
422
423    let arms: Vec<_> = data
424        .variants
425        .iter()
426        .map(|variant| {
427            let name = &variant.ident;
428            let root = container.root;
429            let attrs: Vec<_> = variant
430                .attrs
431                .iter()
432                .map(VariantAttr::parse_one)
433                .flatten_ok()
434                .try_collect()?;
435            let info = VariantInfo::new(container, name, &attrs);
436
437            // For skipped variants, derive an implementation
438            // that always returns an error.
439            if info.is_skipped() {
440                let ty = match &variant.fields {
441                    Fields::Named(_) => VariantTy::Named(info, tag),
442                    Fields::Unnamed(_) => VariantTy::Tuple(info, tag),
443                    Fields::Unit => VariantTy::Unit(info, tag),
444                };
445                let body = SkippedVariantBody::new(ty, pointer);
446                return syn::Result::Ok(quote!(#body));
447            }
448
449            let arm = match &variant.fields {
450                Fields::Named(fields) => {
451                    let fields: Vec<_> = fields
452                        .named
453                        .iter()
454                        .map(|f| NamedFieldInfo::new(container, f))
455                        .try_collect()?;
456                    let bindings = fields.iter().map(|f| {
457                        let binding = f.binding;
458                        quote! { #binding }
459                    });
460                    let body = NamedPointeeBody::new(
461                        NamedPointeeTy::Variant(info, tag),
462                        pointer,
463                        &fields,
464                    );
465                    quote! {
466                        Self::#name { #(#bindings),* } => {
467                            #body
468                        }
469                    }
470                }
471                Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
472                    match tag {
473                        VariantTag::Internal(tag_field) => {
474                            // For internally tagged newtype variants, check the tag field
475                            // before delegating to the inner value.
476                            let key = Ident::new("key", Span::mixed_site());
477                            let effective_name = info.effective_name();
478                            quote! {
479                                Self::#name(inner) => {
480                                    let Some(#key) = #pointer.head() else {
481                                        return Ok(self as &dyn #root::JsonPointee);
482                                    };
483                                    if #key == #tag_field {
484                                        return Ok(&#effective_name as &dyn #root::JsonPointee);
485                                    }
486                                    <_ as #root::JsonPointee>::resolve(inner, #pointer)
487                                }
488                            }
489                        }
490                        VariantTag::External => {
491                            // For externally tagged newtype variants, the first segment
492                            // must match the variant name; then the tail should resolve
493                            // against the inner value.
494                            let key = Ident::new("key", Span::mixed_site());
495                            let effective_name = info.effective_name();
496                            let pointee_ty = TuplePointeeTy::Variant(info, tag);
497                            let key_err = if cfg!(feature = "did-you-mean") {
498                                quote!(#root::JsonPointerKeyError::with_ty(#key, #pointee_ty))
499                            } else {
500                                quote!(#root::JsonPointerKeyError::new(#key))
501                            };
502                            quote! {
503                                Self::#name(inner) => {
504                                    let Some(#key) = #pointer.head() else {
505                                        return Ok(self as &dyn #root::JsonPointee);
506                                    };
507                                    if #key != #effective_name {
508                                        return Err(#key_err)?;
509                                    }
510                                    <_ as #root::JsonPointee>::resolve(inner, #pointer.tail())
511                                }
512                            }
513                        }
514                        VariantTag::Adjacent { tag: tag_field, content: content_field } => {
515                            // For adjacently tagged newtype variants, the first segment
516                            // must match either the tag or content field.
517                            let key = Ident::new("key", Span::mixed_site());
518                            let effective_name = info.effective_name();
519                            let pointee_ty = TuplePointeeTy::Variant(info, tag);
520                            let key_err = if cfg!(feature = "did-you-mean") {
521                                quote!(#root::JsonPointerKeyError::with_suggestions(
522                                    #key,
523                                    #pointee_ty,
524                                    [#tag_field, #content_field],
525                                ))
526                            } else {
527                                quote!(#root::JsonPointerKeyError::new(#key))
528                            };
529                            quote! {
530                                Self::#name(inner) => {
531                                    let Some(#key) = #pointer.head() else {
532                                        return Ok(self as &dyn #root::JsonPointee);
533                                    };
534                                    match &*#key.to_str() {
535                                        #tag_field => Ok(&#effective_name as &dyn #root::JsonPointee),
536                                        #content_field => <_ as #root::JsonPointee>::resolve(inner, #pointer.tail()),
537                                        _ => Err(#key_err)?,
538                                    }
539                                }
540                            }
541                        }
542                        VariantTag::Untagged => {
543                            // For untagged newtype variants, transparently resolve
544                            // against the inner value.
545                            quote! {
546                                Self::#name(inner) => {
547                                    <_ as #root::JsonPointee>::resolve(
548                                        inner,
549                                        #pointer,
550                                    )
551                                }
552                            }
553                        }
554                    }
555                }
556                Fields::Unnamed(fields) => {
557                    let fields: Vec<_> = fields
558                        .unnamed
559                        .iter()
560                        .enumerate()
561                        .map(|(index, f)| TupleFieldInfo::new(index, f))
562                        .try_collect()?;
563                    let bindings = fields.iter().map(|f| {
564                        let binding = &f.binding;
565                        quote! { #binding }
566                    });
567                    let body = TuplePointeeBody::new(
568                        TuplePointeeTy::Variant(info, tag),
569                        pointer,
570                        &fields,
571                    );
572                    quote! {
573                        Self::#name(#(#bindings),*) => {
574                            #body
575                        }
576                    }
577                }
578                Fields::Unit => {
579                    let body = UnitPointeeBody::new(
580                        UnitPointeeTy::Variant(info, tag),
581                        pointer,
582                    );
583                    quote! {
584                        Self::#name => {
585                            #body
586                        }
587                    }
588                }
589            };
590            syn::Result::Ok(arm)
591        })
592        .try_collect()?;
593
594    Ok(quote! {
595        match self {
596            #(#arms,)*
597        }
598    })
599}
600
601/// Extracts the `#[ploidy(pointer(crate = "..."))]` attribute,
602/// falling back to `::ploidy_pointer` if not present.
603fn crate_path(attrs: &[ContainerAttr]) -> Cow<'_, syn::Path> {
604    attrs
605        .iter()
606        .find_map(|attr| match attr {
607            ContainerAttr::Crate(path) => Some(Cow::Borrowed(path)),
608            _ => None,
609        })
610        .unwrap_or_else(|| Cow::Owned(syn::parse_quote!(::ploidy_pointer)))
611}
612
613#[derive(Clone, Copy)]
614struct ContainerInfo<'a> {
615    name: &'a Ident,
616    root: &'a syn::Path,
617    rename_all: Option<RenameAll>,
618    tag: Option<VariantTag<'a>>,
619}
620
621impl<'a> ContainerInfo<'a> {
622    fn new(
623        name: &'a Ident,
624        root: &'a syn::Path,
625        attrs: &'a [ContainerAttr],
626    ) -> Result<Self, DeriveError> {
627        let rename_all = attrs.iter().find_map(|attr| match attr {
628            &ContainerAttr::RenameAll(rename_all) => Some(rename_all),
629            _ => None,
630        });
631
632        let tag = attrs
633            .iter()
634            .filter_map(|attr| match attr {
635                ContainerAttr::Tag(t) => Some(t.as_str()),
636                _ => None,
637            })
638            .at_most_one()
639            .map_err(|_| DeriveError::ConflictingTagAttributes)?;
640        let content = attrs
641            .iter()
642            .filter_map(|attr| match attr {
643                ContainerAttr::Content(c) => Some(c.as_str()),
644                _ => None,
645            })
646            .at_most_one()
647            .map_err(|_| DeriveError::ConflictingTagAttributes)?;
648        let untagged = attrs
649            .iter()
650            .filter(|attr| matches!(attr, ContainerAttr::Untagged))
651            .at_most_one()
652            .map_err(|_| DeriveError::ConflictingTagAttributes)?;
653        let tag = match (tag, content, untagged) {
654            // No explicit tag.
655            (None, None, None) => None,
656            // Internally tagged: only `tag`.
657            (Some(tag), None, None) => Some(VariantTag::Internal(tag)),
658            // Untagged: only `untagged`.
659            (None, None, Some(_)) => Some(VariantTag::Untagged),
660            (Some(tag), Some(content), None) if tag == content => {
661                return Err(DeriveError::SameTagAndContent);
662            }
663            // Adjacently tagged: both `tag` and `content`.
664            (Some(tag), Some(content), None) => Some(VariantTag::Adjacent { tag, content }),
665            (None, Some(_), _) => return Err(DeriveError::ContentWithoutTag),
666            _ => return Err(DeriveError::ConflictingTagAttributes),
667        };
668
669        Ok(Self {
670            name,
671            root,
672            rename_all,
673            tag,
674        })
675    }
676}
677
678#[derive(Debug)]
679struct NamedFieldInfo<'a> {
680    binding: &'a Ident,
681    key: String,
682    is_flattened: bool,
683    is_skipped: bool,
684}
685
686impl<'a> NamedFieldInfo<'a> {
687    fn new(container: ContainerInfo<'a>, f: &'a Field) -> syn::Result<Self> {
688        let name = f.ident.as_ref().unwrap();
689        let attrs: Vec<_> = f
690            .attrs
691            .iter()
692            .map(FieldAttr::parse_one)
693            .flatten_ok()
694            .try_collect()?;
695
696        let is_flattened = attrs.iter().any(|attr| matches!(attr, FieldAttr::Flatten));
697        let is_skipped = attrs.iter().any(|attr| matches!(attr, FieldAttr::Skip));
698
699        if is_flattened && is_skipped {
700            return Err(syn::Error::new_spanned(f, DeriveError::FlattenWithSkip));
701        }
702
703        let key = attrs
704            .iter()
705            .find_map(|attr| match attr {
706                FieldAttr::Rename(name) => Some(name.clone()),
707                _ => None,
708            })
709            .or_else(|| {
710                container
711                    .rename_all
712                    .map(|rename_all| rename_all.apply(&name.to_string()))
713            })
714            .unwrap_or_else(|| name.to_string());
715
716        Ok(NamedFieldInfo {
717            binding: name,
718            key,
719            is_flattened,
720            is_skipped,
721        })
722    }
723}
724
725#[derive(Debug)]
726struct TupleFieldInfo {
727    index: usize,
728    binding: Ident,
729    is_skipped: bool,
730}
731
732impl TupleFieldInfo {
733    fn new(index: usize, f: &Field) -> syn::Result<Self> {
734        let attrs: Vec<_> = f
735            .attrs
736            .iter()
737            .map(FieldAttr::parse_one)
738            .flatten_ok()
739            .try_collect()?;
740
741        let _: () = attrs
742            .iter()
743            .map(|attr| match attr {
744                FieldAttr::Flatten => {
745                    Err(syn::Error::new_spanned(f, DeriveError::FlattenOnNonNamed))
746                }
747                FieldAttr::Rename(_) => {
748                    Err(syn::Error::new_spanned(f, DeriveError::RenameOnNonNamed))
749                }
750                _ => Ok(()),
751            })
752            .try_collect()?;
753
754        let is_skipped = attrs.iter().any(|attr| matches!(attr, FieldAttr::Skip));
755
756        Ok(Self {
757            index,
758            binding: format_ident!("f{}", index, span = Span::mixed_site()),
759            is_skipped,
760        })
761    }
762}
763
764#[derive(Clone, Copy)]
765struct VariantInfo<'a> {
766    container: ContainerInfo<'a>,
767    name: &'a Ident,
768    attrs: &'a [VariantAttr],
769}
770
771impl<'a> VariantInfo<'a> {
772    fn new(container: ContainerInfo<'a>, name: &'a Ident, attrs: &'a [VariantAttr]) -> Self {
773        Self {
774            container,
775            name,
776            attrs,
777        }
778    }
779
780    fn effective_name(&self) -> String {
781        self.attrs
782            .iter()
783            .find_map(|attr| match attr {
784                VariantAttr::Rename(name) => Some(name.clone()),
785                _ => None,
786            })
787            .or_else(|| {
788                self.container
789                    .rename_all
790                    .map(|rename_all| rename_all.apply(&self.name.to_string()))
791            })
792            .unwrap_or_else(|| self.name.to_string())
793    }
794
795    fn is_skipped(&self) -> bool {
796        self.attrs
797            .iter()
798            .any(|attr| matches!(attr, VariantAttr::Skip))
799    }
800}
801
802#[derive(Clone, Copy)]
803struct NamedPointeeBody<'a> {
804    ty: NamedPointeeTy<'a>,
805    pointer: &'a Ident,
806    fields: &'a [NamedFieldInfo<'a>],
807}
808
809impl<'a> NamedPointeeBody<'a> {
810    fn new(ty: NamedPointeeTy<'a>, pointer: &'a Ident, fields: &'a [NamedFieldInfo]) -> Self {
811        Self {
812            ty,
813            pointer,
814            fields,
815        }
816    }
817}
818
819impl ToTokens for NamedPointeeBody<'_> {
820    fn to_tokens(&self, tokens: &mut TokenStream) {
821        let root = self.ty.container().root;
822        let pointer = self.pointer;
823        let key = Ident::new("key", Span::mixed_site());
824        let pointee_ty = self.ty;
825
826        // Build match arms for fields.
827        let arms = self
828            .fields
829            .iter()
830            .filter(|f| !f.is_flattened && !f.is_skipped)
831            .map(|f| {
832                let field_key = &f.key;
833                let binding = f.binding;
834                quote! {
835                    #field_key => <_ as #root::JsonPointee>::resolve(
836                        #binding,
837                        #pointer.tail(),
838                    )
839                }
840            });
841
842        // Build field suggestions for error messages.
843        let mut suggestions: Vec<_> = self
844            .fields
845            .iter()
846            .filter(|f| !f.is_flattened && !f.is_skipped)
847            .map(|f| {
848                let key = &f.key;
849                quote! { #key }
850            })
851            .collect();
852        if let NamedPointeeTy::Variant(_, VariantTag::Internal(tag)) = self.ty {
853            suggestions.push(quote! { #tag });
854        }
855
856        let wildcard = {
857            // For flattened fields, we build an `.or_else()` chain bottom-up
858            // using a right fold.
859            let rest = if cfg!(feature = "did-you-mean") {
860                quote!(Err(#root::JsonPointerKeyError::with_suggestions(
861                    #key,
862                    #pointee_ty,
863                    [#(#suggestions),*],
864                ))?)
865            } else {
866                quote!(Err(#root::JsonPointerKeyError::new(#key))?)
867            };
868            self.fields
869                .iter()
870                .filter(|f| f.is_flattened)
871                .rfold(rest, |rest, f| {
872                    let binding = f.binding;
873                    quote! {
874                        <_ as #root::JsonPointee>
875                            ::resolve(
876                                #binding,
877                                #pointer
878                            )
879                            .or_else(|_| #rest)
880                    }
881                })
882        };
883
884        let body = match self.ty {
885            NamedPointeeTy::Variant(info, VariantTag::Internal(tag_field)) => {
886                // For internally tagged struct-like variants, check the tag field
887                // before resolving against the named fields.
888                let variant_name = info.effective_name();
889                quote! {
890                    let Some(#key) = #pointer.head() else {
891                        return Ok(self as &dyn #root::JsonPointee);
892                    };
893                    if #key == #tag_field {
894                        return Ok(&#variant_name as &dyn #root::JsonPointee);
895                    }
896                    match &*#key.to_str() {
897                        #(#arms,)*
898                        _ => #wildcard,
899                    }
900                }
901            }
902            NamedPointeeTy::Variant(info, VariantTag::External) => {
903                // For externally tagged struct-like variants, the first segment
904                // must match the variant name; then the tail should resolve
905                // against the named fields.
906                let variant_name = info.effective_name();
907                let ty_err = if cfg!(feature = "did-you-mean") {
908                    quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #pointee_ty))
909                } else {
910                    quote!(#root::JsonPointerTypeError::new(&#pointer))
911                };
912                quote! {
913                    let Some(#key) = #pointer.head() else {
914                        return Ok(self as &dyn #root::JsonPointee);
915                    };
916                    if #key != #variant_name {
917                        return Err(#ty_err)?;
918                    }
919                    let #pointer = #pointer.tail();
920                    let Some(#key) = #pointer.head() else {
921                        return Ok(self as &dyn #root::JsonPointee);
922                    };
923                    match &*#key.to_str() {
924                        #(#arms,)*
925                        _ => #wildcard,
926                    }
927                }
928            }
929            NamedPointeeTy::Variant(
930                info,
931                VariantTag::Adjacent {
932                    tag: tag_field,
933                    content: content_field,
934                },
935            ) => {
936                // For adjacently tagged struct-like variants, the first segment
937                // must match either the tag or content field.
938                let variant_name = info.effective_name();
939                let key_err = if cfg!(feature = "did-you-mean") {
940                    quote!(#root::JsonPointerKeyError::with_suggestions(
941                        #key,
942                        #pointee_ty,
943                        [#tag_field, #content_field],
944                    ))
945                } else {
946                    quote!(#root::JsonPointerKeyError::new(#key))
947                };
948                quote! {
949                    let Some(#key) = #pointer.head() else {
950                        return Ok(self as &dyn #root::JsonPointee);
951                    };
952                    match &*#key.to_str() {
953                        #tag_field => {
954                            return Ok(&#variant_name as &dyn #root::JsonPointee);
955                        }
956                        #content_field => {
957                            let #pointer = #pointer.tail();
958                            let Some(#key) = #pointer.head() else {
959                                return Ok(self as &dyn #root::JsonPointee);
960                            };
961                            match &*#key.to_str() {
962                                #(#arms,)*
963                                _ => #wildcard,
964                            }
965                        }
966                        _ => {
967                            return Err(#key_err)?;
968                        }
969                    }
970                }
971            }
972            NamedPointeeTy::Struct(_) | NamedPointeeTy::Variant(_, VariantTag::Untagged) => {
973                // For structs and untagged struct-like variants,
974                // access the fields directly.
975                quote! {
976                    let Some(#key) = #pointer.head() else {
977                        return Ok(self as &dyn #root::JsonPointee);
978                    };
979                    match &*#key.to_str() {
980                        #(#arms,)*
981                        _ => #wildcard,
982                    }
983                }
984            }
985        };
986
987        tokens.append_all(body);
988    }
989}
990
991#[derive(Clone, Copy)]
992struct TuplePointeeBody<'a> {
993    ty: TuplePointeeTy<'a>,
994    pointer: &'a Ident,
995    fields: &'a [TupleFieldInfo],
996}
997
998impl<'a> TuplePointeeBody<'a> {
999    fn new(ty: TuplePointeeTy<'a>, pointer: &'a Ident, fields: &'a [TupleFieldInfo]) -> Self {
1000        Self {
1001            ty,
1002            pointer,
1003            fields,
1004        }
1005    }
1006}
1007
1008impl ToTokens for TuplePointeeBody<'_> {
1009    fn to_tokens(&self, tokens: &mut TokenStream) {
1010        let root = self.ty.container().root;
1011        let pointer = self.pointer;
1012        let idx = Ident::new("idx", Span::mixed_site());
1013        let key = Ident::new("key", Span::mixed_site());
1014
1015        // Build match arms for tuple indices.
1016        let arms = self.fields.iter().filter(|f| !f.is_skipped).map(|f| {
1017            let index = f.index;
1018            let binding = &f.binding;
1019            quote! {
1020                #index => <_ as #root::JsonPointee>::resolve(
1021                    #binding,
1022                    #pointer.tail(),
1023                )
1024            }
1025        });
1026
1027        // Build common tail.
1028        let ty = self.ty;
1029        let len = self.fields.len();
1030        let ty_err = if cfg!(feature = "did-you-mean") {
1031            quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #ty))
1032        } else {
1033            quote!(#root::JsonPointerTypeError::new(&#pointer))
1034        };
1035        let tail = quote! {
1036            let Some(#idx) = #key.to_index() else {
1037                return Err(#ty_err)?;
1038            };
1039            match #idx {
1040                #(#arms,)*
1041                _ => Err(#root::JsonPointeeError::Index(#idx, 0..#len))
1042            }
1043        };
1044
1045        let body = match self.ty {
1046            TuplePointeeTy::Variant(info, VariantTag::Internal(tag_field)) => {
1047                // For internally tagged tuple variants, check the tag field
1048                // before resolving against the tuple indices.
1049                let variant_name = info.effective_name();
1050                quote! {
1051                    let Some(#key) = #pointer.head() else {
1052                        return Ok(self as &dyn #root::JsonPointee);
1053                    };
1054                    if #key == #tag_field {
1055                        return Ok(&#variant_name as &dyn #root::JsonPointee);
1056                    }
1057                    #tail
1058                }
1059            }
1060            TuplePointeeTy::Variant(info, VariantTag::External) => {
1061                // For externally tagged tuple variants, the first segment
1062                // must match the variant name; then the tail should resolve
1063                // against the tuple indices.
1064                let variant_name = info.effective_name();
1065                let ty_err = if cfg!(feature = "did-you-mean") {
1066                    quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #ty))
1067                } else {
1068                    quote!(#root::JsonPointerTypeError::new(&#pointer))
1069                };
1070                quote! {
1071                    let Some(#key) = #pointer.head() else {
1072                        return Ok(self as &dyn #root::JsonPointee);
1073                    };
1074                    if #key != #variant_name {
1075                        return Err(#ty_err)?;
1076                    }
1077                    let #pointer = #pointer.tail();
1078                    let Some(#key) = #pointer.head() else {
1079                        return Ok(self as &dyn #root::JsonPointee);
1080                    };
1081                    #tail
1082                }
1083            }
1084            TuplePointeeTy::Variant(
1085                info,
1086                VariantTag::Adjacent {
1087                    tag: tag_field,
1088                    content: content_field,
1089                },
1090            ) => {
1091                // For adjacently tagged tuple variants, the first segment
1092                // must match either the tag or content field.
1093                let variant_name = info.effective_name();
1094                let key_err = if cfg!(feature = "did-you-mean") {
1095                    quote!(#root::JsonPointerKeyError::with_suggestions(
1096                        #key,
1097                        #ty,
1098                        [#tag_field, #content_field],
1099                    ))
1100                } else {
1101                    quote!(#root::JsonPointerKeyError::new(#key))
1102                };
1103                quote! {
1104                    let Some(#key) = #pointer.head() else {
1105                        return Ok(self as &dyn #root::JsonPointee);
1106                    };
1107                    match &*#key.to_str() {
1108                        #tag_field => {
1109                            return Ok(&#variant_name as &dyn #root::JsonPointee);
1110                        }
1111                        #content_field => {
1112                            let #pointer = #pointer.tail();
1113                            let Some(#key) = #pointer.head() else {
1114                                return Ok(self as &dyn #root::JsonPointee);
1115                            };
1116                            #tail
1117                        }
1118                        _ => {
1119                            return Err(#key_err)?;
1120                        }
1121                    }
1122                }
1123            }
1124            TuplePointeeTy::Struct(_) | TuplePointeeTy::Variant(_, VariantTag::Untagged) => {
1125                // For structs and untagged tuple variants,
1126                // access the tuple indices directly.
1127                quote! {
1128                    let Some(#key) = #pointer.head() else {
1129                        return Ok(self as &dyn #root::JsonPointee);
1130                    };
1131                    #tail
1132                }
1133            }
1134        };
1135
1136        tokens.append_all(body);
1137    }
1138}
1139
1140#[derive(Clone, Copy)]
1141struct UnitPointeeBody<'a> {
1142    ty: UnitPointeeTy<'a>,
1143    pointer: &'a Ident,
1144}
1145
1146impl<'a> UnitPointeeBody<'a> {
1147    fn new(ty: UnitPointeeTy<'a>, pointer: &'a Ident) -> Self {
1148        Self { ty, pointer }
1149    }
1150}
1151
1152impl ToTokens for UnitPointeeBody<'_> {
1153    fn to_tokens(&self, tokens: &mut TokenStream) {
1154        let root = self.ty.container().root;
1155        let pointer = self.pointer;
1156        let body = match self.ty {
1157            ty @ UnitPointeeTy::Variant(info, VariantTag::Internal(tag_field)) => {
1158                // For internally tagged unit variants, only the tag field is accessible.
1159                let key = Ident::new("key", Span::mixed_site());
1160                let variant_name = info.effective_name();
1161                let key_err = if cfg!(feature = "did-you-mean") {
1162                    quote!(#root::JsonPointerKeyError::with_suggestions(
1163                        #key,
1164                        #ty,
1165                        [#tag_field],
1166                    ))
1167                } else {
1168                    quote!(#root::JsonPointerKeyError::new(#key))
1169                };
1170                quote! {
1171                    let Some(#key) = #pointer.head() else {
1172                        return Ok(self as &dyn #root::JsonPointee);
1173                    };
1174                    if #key == #tag_field {
1175                        return Ok(&#variant_name as &dyn #root::JsonPointee);
1176                    }
1177                    Err(#key_err)?
1178                }
1179            }
1180            ty @ UnitPointeeTy::Variant(info, VariantTag::External) => {
1181                // For externally tagged unit variants, allow just the tag field.
1182                let key = Ident::new("key", Span::mixed_site());
1183                let variant_name = info.effective_name();
1184                let key_err = if cfg!(feature = "did-you-mean") {
1185                    quote!(#root::JsonPointerKeyError::with_ty(#key, #ty))
1186                } else {
1187                    quote!(#root::JsonPointerKeyError::new(#key))
1188                };
1189                let ty_err = if cfg!(feature = "did-you-mean") {
1190                    quote!(#root::JsonPointerTypeError::with_ty(&#pointer.tail(), #ty))
1191                } else {
1192                    quote!(#root::JsonPointerTypeError::new(&#pointer.tail()))
1193                };
1194                quote! {
1195                    let Some(#key) = #pointer.head() else {
1196                        return Ok(self as &dyn #root::JsonPointee);
1197                    };
1198                    if #key != #variant_name {
1199                        return Err(#key_err)?;
1200                    }
1201                    if !#pointer.tail().is_empty() {
1202                        return Err(#ty_err)?;
1203                    }
1204                    Ok(self as &dyn #root::JsonPointee)
1205                }
1206            }
1207            ty @ UnitPointeeTy::Variant(info, VariantTag::Adjacent { tag: tag_field, .. }) => {
1208                // For adjacently tagged unit variants, allow just the tag field.
1209                let key = Ident::new("key", Span::mixed_site());
1210                let variant_name = info.effective_name();
1211                let key_err = if cfg!(feature = "did-you-mean") {
1212                    quote!(#root::JsonPointerKeyError::with_suggestions(
1213                        #key,
1214                        #ty,
1215                        [#tag_field],
1216                    ))
1217                } else {
1218                    quote!(#root::JsonPointerKeyError::new(#key))
1219                };
1220                quote! {
1221                    let Some(#key) = #pointer.head() else {
1222                        return Ok(self as &dyn #root::JsonPointee);
1223                    };
1224                    match &*#key.to_str() {
1225                        #tag_field => {
1226                            return Ok(&#variant_name as &dyn #root::JsonPointee);
1227                        }
1228                        _ => {
1229                            return Err(#key_err)?;
1230                        }
1231                    }
1232                }
1233            }
1234            ty @ (UnitPointeeTy::Struct(_) | UnitPointeeTy::Variant(_, VariantTag::Untagged)) => {
1235                // For unit structs and untagged unit variants, deny all fields.
1236                let ty_err = if cfg!(feature = "did-you-mean") {
1237                    quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #ty))
1238                } else {
1239                    quote!(#root::JsonPointerTypeError::new(&#pointer))
1240                };
1241                quote! {
1242                    if #pointer.is_empty() {
1243                        Ok(self as &dyn #root::JsonPointee)
1244                    } else {
1245                        Err(#ty_err)?
1246                    }
1247                }
1248            }
1249        };
1250        tokens.append_all(body);
1251    }
1252}
1253
1254#[derive(Clone, Copy)]
1255enum NamedPointeeTy<'a> {
1256    Struct(ContainerInfo<'a>),
1257    Variant(VariantInfo<'a>, VariantTag<'a>),
1258}
1259
1260impl<'a> NamedPointeeTy<'a> {
1261    fn container(self) -> ContainerInfo<'a> {
1262        match self {
1263            Self::Struct(info) => info,
1264            Self::Variant(info, _) => info.container,
1265        }
1266    }
1267}
1268
1269impl ToTokens for NamedPointeeTy<'_> {
1270    fn to_tokens(&self, tokens: &mut TokenStream) {
1271        let root = self.container().root;
1272        tokens.append_all(match self {
1273            Self::Struct(info) => {
1274                let ty = info.name;
1275                quote! {
1276                    #root::JsonPointeeType::struct_named(
1277                        stringify!(#ty)
1278                    )
1279                }
1280            }
1281            Self::Variant(info, ..) => {
1282                let ty = info.container.name;
1283                let variant = info.name;
1284                quote! {
1285                    #root::JsonPointeeType::struct_variant_named(
1286                        stringify!(#ty),
1287                        stringify!(#variant),
1288                    )
1289                }
1290            }
1291        });
1292    }
1293}
1294
1295#[derive(Clone, Copy)]
1296enum TuplePointeeTy<'a> {
1297    Struct(ContainerInfo<'a>),
1298    Variant(VariantInfo<'a>, VariantTag<'a>),
1299}
1300
1301impl<'a> TuplePointeeTy<'a> {
1302    fn container(self) -> ContainerInfo<'a> {
1303        match self {
1304            Self::Struct(info) => info,
1305            Self::Variant(info, _) => info.container,
1306        }
1307    }
1308}
1309
1310impl ToTokens for TuplePointeeTy<'_> {
1311    fn to_tokens(&self, tokens: &mut TokenStream) {
1312        let root = self.container().root;
1313        tokens.append_all(match self {
1314            Self::Struct(info) => {
1315                let ty = info.name;
1316                quote! {
1317                    #root::JsonPointeeType::tuple_struct_named(
1318                        stringify!(#ty)
1319                    )
1320                }
1321            }
1322            Self::Variant(info, ..) => {
1323                let ty = info.container.name;
1324                let variant = info.name;
1325                quote! {
1326                    #root::JsonPointeeType::tuple_variant_named(
1327                        stringify!(#ty),
1328                        stringify!(#variant),
1329                    )
1330                }
1331            }
1332        });
1333    }
1334}
1335
1336#[derive(Clone, Copy)]
1337enum UnitPointeeTy<'a> {
1338    Struct(ContainerInfo<'a>),
1339    Variant(VariantInfo<'a>, VariantTag<'a>),
1340}
1341
1342impl<'a> UnitPointeeTy<'a> {
1343    fn container(self) -> ContainerInfo<'a> {
1344        match self {
1345            Self::Struct(info) => info,
1346            Self::Variant(info, _) => info.container,
1347        }
1348    }
1349}
1350
1351impl ToTokens for UnitPointeeTy<'_> {
1352    fn to_tokens(&self, tokens: &mut TokenStream) {
1353        let root = self.container().root;
1354        tokens.append_all(match self {
1355            Self::Struct(info) => {
1356                let ty = info.name;
1357                quote! {
1358                    #root::JsonPointeeType::unit_struct_named(
1359                        stringify!(#ty)
1360                    )
1361                }
1362            }
1363            Self::Variant(info, ..) => {
1364                let ty = info.container.name;
1365                let variant = info.name;
1366                quote! {
1367                    #root::JsonPointeeType::unit_variant_named(
1368                        stringify!(#ty),
1369                        stringify!(#variant),
1370                    )
1371                }
1372            }
1373        });
1374    }
1375}
1376
1377#[derive(Clone, Copy)]
1378enum VariantTy<'a> {
1379    Named(VariantInfo<'a>, VariantTag<'a>),
1380    Tuple(VariantInfo<'a>, VariantTag<'a>),
1381    Unit(VariantInfo<'a>, VariantTag<'a>),
1382}
1383
1384impl<'a> VariantTy<'a> {
1385    fn info(self) -> VariantInfo<'a> {
1386        let (Self::Named(info, _) | Self::Tuple(info, _) | Self::Unit(info, _)) = self;
1387        info
1388    }
1389
1390    fn tag(self) -> VariantTag<'a> {
1391        let (Self::Named(_, tag) | Self::Tuple(_, tag) | Self::Unit(_, tag)) = self;
1392        tag
1393    }
1394}
1395
1396impl ToTokens for VariantTy<'_> {
1397    fn to_tokens(&self, tokens: &mut TokenStream) {
1398        let root = self.info().container.root;
1399        tokens.append_all(match self {
1400            Self::Named(info, _) => {
1401                let ty = info.container.name;
1402                let variant = info.name;
1403                quote! {
1404                    #root::JsonPointeeType::struct_variant_named(
1405                        stringify!(#ty),
1406                        stringify!(#variant),
1407                    )
1408                }
1409            }
1410            Self::Tuple(info, _) => {
1411                let ty = info.container.name;
1412                let variant = info.name;
1413                quote! {
1414                    #root::JsonPointeeType::tuple_variant_named(
1415                        stringify!(#ty),
1416                        stringify!(#variant),
1417                    )
1418                }
1419            }
1420            Self::Unit(info, _) => {
1421                let ty = info.container.name;
1422                let variant = info.name;
1423                quote! {
1424                    #root::JsonPointeeType::unit_variant_named(
1425                        stringify!(#ty),
1426                        stringify!(#variant),
1427                    )
1428                }
1429            }
1430        });
1431    }
1432}
1433
1434#[derive(Clone, Copy)]
1435struct SkippedVariantBody<'a> {
1436    ty: VariantTy<'a>,
1437    pointer: &'a Ident,
1438}
1439
1440impl<'a> SkippedVariantBody<'a> {
1441    fn new(ty: VariantTy<'a>, pointer: &'a Ident) -> Self {
1442        Self { ty, pointer }
1443    }
1444}
1445
1446impl ToTokens for SkippedVariantBody<'_> {
1447    fn to_tokens(&self, tokens: &mut TokenStream) {
1448        let root = self.ty.info().container.root;
1449        let pointer = self.pointer;
1450        let ty = self.ty;
1451
1452        let pattern = match ty {
1453            VariantTy::Named(info, _) => {
1454                let variant_name = info.name;
1455                quote!(Self::#variant_name { .. })
1456            }
1457            VariantTy::Tuple(info, _) => {
1458                let variant_name = info.name;
1459                quote!(Self::#variant_name(..))
1460            }
1461            VariantTy::Unit(info, _) => {
1462                let variant_name = info.name;
1463                quote!(Self::#variant_name)
1464            }
1465        };
1466
1467        match ty.tag() {
1468            VariantTag::Internal(tag_field) => {
1469                // Internally tagged skipped variants allow access to the tag field only.
1470                let key = Ident::new("key", Span::mixed_site());
1471                let effective_name = ty.info().effective_name();
1472                let ty_err = if cfg!(feature = "did-you-mean") {
1473                    quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #ty))
1474                } else {
1475                    quote!(#root::JsonPointerTypeError::new(&#pointer))
1476                };
1477                tokens.append_all(quote! {
1478                    #pattern => {
1479                        let Some(#key) = #pointer.head() else {
1480                            return Ok(self as &dyn #root::JsonPointee);
1481                        };
1482                        if #key == #tag_field {
1483                            return Ok(&#effective_name as &dyn #root::JsonPointee);
1484                        }
1485                        Err(#ty_err)?
1486                    }
1487                });
1488            }
1489            VariantTag::External => {
1490                // Externally tagged skipped variants are completely inaccessible.
1491                let ty_err = if cfg!(feature = "did-you-mean") {
1492                    quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #ty))
1493                } else {
1494                    quote!(#root::JsonPointerTypeError::new(&#pointer))
1495                };
1496                tokens.append_all(quote! {
1497                    #pattern => Err(#ty_err)?
1498                });
1499            }
1500            VariantTag::Adjacent { tag: tag_field, .. } => {
1501                // Adjacently tagged skipped variants allow tag field access,
1502                // but content field access errors.
1503                let key = Ident::new("key", Span::mixed_site());
1504                let effective_name = ty.info().effective_name();
1505                let key_err = if cfg!(feature = "did-you-mean") {
1506                    quote!(#root::JsonPointerKeyError::with_suggestions(
1507                        #key,
1508                        #ty,
1509                        [#tag_field],
1510                    ))
1511                } else {
1512                    quote!(#root::JsonPointerKeyError::new(#key))
1513                };
1514                tokens.append_all(quote! {
1515                    #pattern => {
1516                        let Some(#key) = #pointer.head() else {
1517                            return Ok(self as &dyn #root::JsonPointee);
1518                        };
1519                        match &*#key.to_str() {
1520                            #tag_field => {
1521                                return Ok(&#effective_name as &dyn #root::JsonPointee);
1522                            }
1523                            _ => {
1524                                return Err(#key_err)?;
1525                            }
1526                        }
1527                    }
1528                });
1529            }
1530            VariantTag::Untagged => {
1531                // Untagged skipped variants are completely inaccessible.
1532                let ty_err = if cfg!(feature = "did-you-mean") {
1533                    quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #ty))
1534                } else {
1535                    quote!(#root::JsonPointerTypeError::new(&#pointer))
1536                };
1537                tokens.append_all(quote! {
1538                    #pattern => Err(#ty_err)?
1539                });
1540            }
1541        }
1542    }
1543}
1544
1545#[derive(Clone, Copy, Debug)]
1546enum VariantTag<'a> {
1547    Internal(&'a str),
1548    External,
1549    Adjacent { tag: &'a str, content: &'a str },
1550    Untagged,
1551}
1552
1553#[derive(Clone)]
1554enum ContainerAttr {
1555    Crate(syn::Path),
1556    RenameAll(RenameAll),
1557    Tag(String),
1558    Content(String),
1559    Untagged,
1560}
1561
1562impl ContainerAttr {
1563    fn parse_all(attrs: &[Attribute]) -> syn::Result<Vec<Self>> {
1564        attrs
1565            .iter()
1566            .map(ContainerAttr::parse_one)
1567            .flatten_ok()
1568            .try_collect()
1569    }
1570
1571    fn parse_one(attr: &Attribute) -> syn::Result<Vec<Self>> {
1572        if !attr.path().is_ident("ploidy") {
1573            return Ok(vec![]);
1574        }
1575        let mut attrs = vec![];
1576        attr.parse_nested_meta(|meta| {
1577            if meta.path.is_ident("pointer") {
1578                meta.parse_nested_meta(|meta| {
1579                    if meta.path.is_ident("crate") {
1580                        let value = meta.value()?;
1581                        let s: syn::LitStr = value.parse()?;
1582                        attrs.push(Self::Crate(s.parse()?));
1583                    } else if meta.path.is_ident("rename_all") {
1584                        let value = meta.value()?;
1585                        let s: syn::LitStr = value.parse()?;
1586                        let Some(rename) = RenameAll::from_str(&s.value()) else {
1587                            return Err(meta.error(DeriveError::BadRenameAll));
1588                        };
1589                        attrs.push(Self::RenameAll(rename));
1590                    } else if meta.path.is_ident("tag") {
1591                        let value = meta.value()?;
1592                        let s: syn::LitStr = value.parse()?;
1593                        attrs.push(Self::Tag(s.value()));
1594                    } else if meta.path.is_ident("content") {
1595                        let value = meta.value()?;
1596                        let s: syn::LitStr = value.parse()?;
1597                        attrs.push(Self::Content(s.value()));
1598                    } else if meta.path.is_ident("untagged") {
1599                        attrs.push(Self::Untagged);
1600                    } else {
1601                        return Err(meta.error(DeriveError::UnrecognizedPointer));
1602                    }
1603                    Ok(())
1604                })?;
1605            } else {
1606                return Err(meta.error(DeriveError::UnrecognizedPloidy));
1607            }
1608            Ok(())
1609        })?;
1610        Ok(attrs)
1611    }
1612}
1613
1614#[derive(Clone, Debug)]
1615enum FieldAttr {
1616    Rename(String),
1617    Flatten,
1618    Skip,
1619}
1620
1621impl FieldAttr {
1622    fn parse_one(attr: &Attribute) -> syn::Result<Vec<Self>> {
1623        if !attr.path().is_ident("ploidy") {
1624            return Ok(vec![]);
1625        }
1626        let mut attrs = vec![];
1627        attr.parse_nested_meta(|meta| {
1628            if meta.path.is_ident("pointer") {
1629                meta.parse_nested_meta(|meta| {
1630                    if meta.path.is_ident("rename") {
1631                        let value = meta.value()?;
1632                        let s: syn::LitStr = value.parse()?;
1633                        attrs.push(Self::Rename(s.value()));
1634                    } else if meta.path.is_ident("flatten") {
1635                        attrs.push(Self::Flatten);
1636                    } else if meta.path.is_ident("skip") {
1637                        attrs.push(Self::Skip);
1638                    } else {
1639                        return Err(meta.error(DeriveError::UnrecognizedPointer));
1640                    }
1641                    Ok(())
1642                })?;
1643            } else {
1644                return Err(meta.error(DeriveError::UnrecognizedPloidy));
1645            }
1646            Ok(())
1647        })?;
1648        Ok(attrs)
1649    }
1650}
1651
1652#[derive(Clone, Debug)]
1653enum VariantAttr {
1654    Skip,
1655    Rename(String),
1656}
1657
1658impl VariantAttr {
1659    fn parse_one(attr: &Attribute) -> syn::Result<Vec<Self>> {
1660        if !attr.path().is_ident("ploidy") {
1661            return Ok(vec![]);
1662        }
1663        let mut attrs = vec![];
1664        attr.parse_nested_meta(|meta| {
1665            if meta.path.is_ident("pointer") {
1666                meta.parse_nested_meta(|meta| {
1667                    if meta.path.is_ident("skip") {
1668                        attrs.push(Self::Skip);
1669                    } else if meta.path.is_ident("rename") {
1670                        let value = meta.value()?;
1671                        let s: syn::LitStr = value.parse()?;
1672                        attrs.push(Self::Rename(s.value()));
1673                    } else {
1674                        return Err(meta.error(DeriveError::UnrecognizedPointer));
1675                    }
1676                    Ok(())
1677                })?;
1678            } else {
1679                return Err(meta.error(DeriveError::UnrecognizedPloidy));
1680            }
1681            Ok(())
1682        })?;
1683        Ok(attrs)
1684    }
1685}
1686
1687/// Supported `rename_all` transforms, matching Serde.
1688#[derive(Clone, Copy, Debug)]
1689enum RenameAll {
1690    Lowercase,
1691    Uppercase,
1692    PascalCase,
1693    CamelCase,
1694    SnakeCase,
1695    ScreamingSnakeCase,
1696    KebabCase,
1697    ScreamingKebabCase,
1698}
1699
1700impl RenameAll {
1701    const fn all() -> &'static [Self] {
1702        &[
1703            Self::Lowercase,
1704            Self::Uppercase,
1705            Self::PascalCase,
1706            Self::CamelCase,
1707            Self::SnakeCase,
1708            Self::ScreamingSnakeCase,
1709            Self::KebabCase,
1710            Self::ScreamingKebabCase,
1711        ]
1712    }
1713
1714    fn from_str(s: &str) -> Option<Self> {
1715        Some(match s {
1716            "lowercase" => RenameAll::Lowercase,
1717            "UPPERCASE" => RenameAll::Uppercase,
1718            "PascalCase" => RenameAll::PascalCase,
1719            "camelCase" => RenameAll::CamelCase,
1720            "snake_case" => RenameAll::SnakeCase,
1721            "SCREAMING_SNAKE_CASE" => RenameAll::ScreamingSnakeCase,
1722            "kebab-case" => RenameAll::KebabCase,
1723            "SCREAMING-KEBAB-CASE" => RenameAll::ScreamingKebabCase,
1724            _ => return None,
1725        })
1726    }
1727
1728    fn apply(&self, s: &str) -> String {
1729        match self {
1730            RenameAll::Lowercase => s.to_lowercase(),
1731            RenameAll::Uppercase => s.to_uppercase(),
1732            RenameAll::PascalCase => s.to_pascal_case(),
1733            RenameAll::CamelCase => s.to_lower_camel_case(),
1734            RenameAll::SnakeCase => s.to_snake_case(),
1735            RenameAll::ScreamingSnakeCase => s.to_shouty_snake_case(),
1736            RenameAll::KebabCase => s.to_kebab_case(),
1737            RenameAll::ScreamingKebabCase => s.to_shouty_kebab_case(),
1738        }
1739    }
1740}
1741
1742impl Display for RenameAll {
1743    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1744        f.write_str(match self {
1745            Self::Lowercase => "lowercase",
1746            Self::Uppercase => "UPPERCASE",
1747            Self::PascalCase => "PascalCase",
1748            Self::CamelCase => "camelCase",
1749            Self::SnakeCase => "snake_case",
1750            Self::ScreamingSnakeCase => "SCREAMING_SNAKE_CASE",
1751            Self::KebabCase => "kebab-case",
1752            Self::ScreamingKebabCase => "SCREAMING-KEBAB-CASE",
1753        })
1754    }
1755}
1756
1757#[derive(Debug, thiserror::Error)]
1758enum DeriveError {
1759    #[error("`JsonPointee` can't be derived for unions")]
1760    Union,
1761    #[error("`rename` is only supported on struct and struct-like enum variant fields")]
1762    RenameOnNonNamed,
1763    #[error("`flatten` is only supported on struct and struct-like enum variant fields")]
1764    FlattenOnNonNamed,
1765    #[error("`flatten` and `skip` are mutually exclusive")]
1766    FlattenWithSkip,
1767    #[error("`tag` is only supported on enums")]
1768    TagOnNonEnum,
1769    #[error("`content` requires `tag`")]
1770    ContentWithoutTag,
1771    #[error("`tag` and `content` must have different field names")]
1772    SameTagAndContent,
1773    #[error("only one of: `tag`, `tag` and `content`, `untagged` allowed")]
1774    ConflictingTagAttributes,
1775    #[error("`rename_all` must be one of: {}", RenameAll::all().iter().join(","))]
1776    BadRenameAll,
1777    #[error("unrecognized `#[ploidy(...)]` attribute")]
1778    UnrecognizedPloidy,
1779    #[error("unrecognized `#[ploidy(pointer(...))]` attribute")]
1780    UnrecognizedPointer,
1781}