use std::{borrow::Cow, fmt::Display};
use heck::{
ToKebabCase, ToLowerCamelCase, ToPascalCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase,
};
use itertools::Itertools;
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, TokenStreamExt, format_ident, quote};
use syn::{
Attribute, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, GenericParam, Ident,
Lifetime, parse_macro_input,
};
#[proc_macro_derive(JsonPointee, attributes(ploidy))]
pub fn derive_pointee(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
derive_pointee_for(&input)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
#[proc_macro_derive(JsonPointerTarget, attributes(ploidy))]
pub fn derive_pointer_target(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
derive_pointer_target_for(&input)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
fn derive_pointee_for(input: &DeriveInput) -> syn::Result<TokenStream> {
let name = &input.ident;
let attrs = ContainerAttr::parse_all(&input.attrs)?;
let root = crate_path(&attrs);
let container = ContainerInfo::new(name, &root, &attrs)
.map_err(|err| syn::Error::new_spanned(input, err))?;
let pointer = Ident::new("pointer", Span::mixed_site());
let body = match &input.data {
Data::Struct(data) => {
if container.tag.is_some() {
return Err(syn::Error::new_spanned(input, DeriveError::TagOnNonEnum));
}
derive_for_struct(&pointer, container, data)?
}
Data::Enum(data) => derive_for_enum(&pointer, container, data)?,
Data::Union(_) => return Err(syn::Error::new_spanned(input, DeriveError::Union)),
};
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let where_clause = {
let type_param_bounds = input
.generics
.params
.iter()
.filter_map(|param| match param {
GenericParam::Type(param) => {
let ident = ¶m.ident;
Some(quote! { #ident: #root::JsonPointee })
}
_ => None,
})
.collect_vec();
if type_param_bounds.is_empty() {
quote! { #where_clause }
} else if let Some(where_clause) = where_clause {
quote! { #where_clause #(#type_param_bounds),* }
} else {
quote! { where #(#type_param_bounds),* }
}
};
Ok(quote! {
#[automatically_derived]
impl #impl_generics #root::JsonPointee for #name #ty_generics #where_clause {
fn resolve(&self, #pointer: &#root::JsonPointer)
-> ::std::result::Result<&dyn #root::JsonPointee, #root::JsonPointeeError> {
#body
}
}
})
}
fn derive_pointer_target_for(input: &DeriveInput) -> syn::Result<TokenStream> {
let name = &input.ident;
let attrs = ContainerAttr::parse_all(&input.attrs)?;
let root = crate_path(&attrs);
let lifetime = Lifetime::new("'pointee", Span::mixed_site());
let (_, ty_generics, where_clause) = input.generics.split_for_impl();
let generics = {
let mut generics = input.generics.clone();
generics.params.push(syn::parse_quote!(#lifetime));
generics
};
let (impl_generics, _, _) = generics.split_for_impl();
let where_clause = match where_clause {
Some(where_clause) => {
let s = quote! { #name #ty_generics: ::std::any::Any };
quote! { #where_clause, #s }
}
None => quote! { where #name #ty_generics: ::std::any::Any },
};
Ok(quote! {
#[automatically_derived]
impl #impl_generics #root::JsonPointerTarget<#lifetime>
for &#lifetime #name #ty_generics
#where_clause
{
#[inline]
fn from_pointee(
pointee: &#lifetime dyn #root::JsonPointee,
) -> ::std::result::Result<Self, #root::JsonPointerTargetError> {
let any: &dyn ::std::any::Any = pointee;
any.downcast_ref().ok_or_else(|| #root::JsonPointerTargetError {
expected: ::std::any::type_name::<#name #ty_generics>(),
actual: pointee.name(),
})
}
}
})
}
fn derive_for_struct(
pointer: &Ident,
container: ContainerInfo<'_>,
data: &DataStruct,
) -> syn::Result<TokenStream> {
let body = match &data.fields {
Fields::Named(fields) => {
let fields: Vec<_> = fields
.named
.iter()
.map(|f| NamedFieldInfo::new(container, f))
.try_collect()?;
let bindings = fields.iter().map(|f| {
let binding = f.binding;
quote! { #binding }
});
let body = NamedPointeeBody::new(NamedPointeeTy::Struct(container), pointer, &fields);
quote! {
let Self { #(#bindings),* } = self;
#body
}
}
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
let root = container.root;
quote! {
<_ as #root::JsonPointee>::resolve(&self.0, #pointer)
}
}
Fields::Unnamed(fields) => {
let fields: Vec<_> = fields
.unnamed
.iter()
.enumerate()
.map(|(index, f)| TupleFieldInfo::new(index, f))
.try_collect()?;
let bindings = fields.iter().map(|f| {
let binding = &f.binding;
quote! { #binding }
});
let body = TuplePointeeBody::new(TuplePointeeTy::Struct(container), pointer, &fields);
quote! {
let Self(#(#bindings),*) = self;
#body
}
}
Fields::Unit => {
let body = UnitPointeeBody::new(UnitPointeeTy::Struct(container), pointer);
quote!(#body)
}
};
Ok(body)
}
fn derive_for_enum(
pointer: &Ident,
container: ContainerInfo<'_>,
data: &DataEnum,
) -> syn::Result<TokenStream> {
let tag = container.tag.unwrap_or(VariantTag::External);
let arms: Vec<_> = data
.variants
.iter()
.map(|variant| {
let name = &variant.ident;
let root = container.root;
let attrs: Vec<_> = variant
.attrs
.iter()
.map(VariantAttr::parse_one)
.flatten_ok()
.try_collect()?;
let info = VariantInfo::new(container, name, &attrs);
if info.is_skipped() {
let ty = match &variant.fields {
Fields::Named(_) => VariantTy::Named(info, tag),
Fields::Unnamed(_) => VariantTy::Tuple(info, tag),
Fields::Unit => VariantTy::Unit(info, tag),
};
let body = SkippedVariantBody::new(ty, pointer);
return syn::Result::Ok(quote!(#body));
}
let arm = match &variant.fields {
Fields::Named(fields) => {
let fields: Vec<_> = fields
.named
.iter()
.map(|f| NamedFieldInfo::new(container, f))
.try_collect()?;
let bindings = fields.iter().map(|f| {
let binding = f.binding;
quote! { #binding }
});
let body = NamedPointeeBody::new(
NamedPointeeTy::Variant(info, tag),
pointer,
&fields,
);
quote! {
Self::#name { #(#bindings),* } => {
#body
}
}
}
Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
match tag {
VariantTag::Internal(tag_field) => {
let key = Ident::new("key", Span::mixed_site());
let effective_name = info.effective_name();
quote! {
Self::#name(inner) => {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
if #key == #tag_field {
return Ok(&#effective_name as &dyn #root::JsonPointee);
}
<_ as #root::JsonPointee>::resolve(inner, #pointer)
}
}
}
VariantTag::External => {
let key = Ident::new("key", Span::mixed_site());
let effective_name = info.effective_name();
let pointee_ty = TuplePointeeTy::Variant(info, tag);
let key_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerKeyError::with_ty(#key, #pointee_ty))
} else {
quote!(#root::JsonPointerKeyError::new(#key))
};
quote! {
Self::#name(inner) => {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
if #key != #effective_name {
return Err(#key_err)?;
}
<_ as #root::JsonPointee>::resolve(inner, #pointer.tail())
}
}
}
VariantTag::Adjacent { tag: tag_field, content: content_field } => {
let key = Ident::new("key", Span::mixed_site());
let effective_name = info.effective_name();
let pointee_ty = TuplePointeeTy::Variant(info, tag);
let key_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerKeyError::with_suggestions(
#key,
#pointee_ty,
[#tag_field, #content_field],
))
} else {
quote!(#root::JsonPointerKeyError::new(#key))
};
quote! {
Self::#name(inner) => {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
match &*#key.to_str() {
#tag_field => Ok(&#effective_name as &dyn #root::JsonPointee),
#content_field => <_ as #root::JsonPointee>::resolve(inner, #pointer.tail()),
_ => Err(#key_err)?,
}
}
}
}
VariantTag::Untagged => {
quote! {
Self::#name(inner) => {
<_ as #root::JsonPointee>::resolve(
inner,
#pointer,
)
}
}
}
}
}
Fields::Unnamed(fields) => {
let fields: Vec<_> = fields
.unnamed
.iter()
.enumerate()
.map(|(index, f)| TupleFieldInfo::new(index, f))
.try_collect()?;
let bindings = fields.iter().map(|f| {
let binding = &f.binding;
quote! { #binding }
});
let body = TuplePointeeBody::new(
TuplePointeeTy::Variant(info, tag),
pointer,
&fields,
);
quote! {
Self::#name(#(#bindings),*) => {
#body
}
}
}
Fields::Unit => {
let body = UnitPointeeBody::new(
UnitPointeeTy::Variant(info, tag),
pointer,
);
quote! {
Self::#name => {
#body
}
}
}
};
syn::Result::Ok(arm)
})
.try_collect()?;
Ok(quote! {
match self {
#(#arms,)*
}
})
}
fn crate_path(attrs: &[ContainerAttr]) -> Cow<'_, syn::Path> {
attrs
.iter()
.find_map(|attr| match attr {
ContainerAttr::Crate(path) => Some(Cow::Borrowed(path)),
_ => None,
})
.unwrap_or_else(|| Cow::Owned(syn::parse_quote!(::ploidy_pointer)))
}
#[derive(Clone, Copy)]
struct ContainerInfo<'a> {
name: &'a Ident,
root: &'a syn::Path,
rename_all: Option<RenameAll>,
tag: Option<VariantTag<'a>>,
}
impl<'a> ContainerInfo<'a> {
fn new(
name: &'a Ident,
root: &'a syn::Path,
attrs: &'a [ContainerAttr],
) -> Result<Self, DeriveError> {
let rename_all = attrs.iter().find_map(|attr| match attr {
&ContainerAttr::RenameAll(rename_all) => Some(rename_all),
_ => None,
});
let tag = attrs
.iter()
.filter_map(|attr| match attr {
ContainerAttr::Tag(t) => Some(t.as_str()),
_ => None,
})
.at_most_one()
.map_err(|_| DeriveError::ConflictingTagAttributes)?;
let content = attrs
.iter()
.filter_map(|attr| match attr {
ContainerAttr::Content(c) => Some(c.as_str()),
_ => None,
})
.at_most_one()
.map_err(|_| DeriveError::ConflictingTagAttributes)?;
let untagged = attrs
.iter()
.filter(|attr| matches!(attr, ContainerAttr::Untagged))
.at_most_one()
.map_err(|_| DeriveError::ConflictingTagAttributes)?;
let tag = match (tag, content, untagged) {
(None, None, None) => None,
(Some(tag), None, None) => Some(VariantTag::Internal(tag)),
(None, None, Some(_)) => Some(VariantTag::Untagged),
(Some(tag), Some(content), None) if tag == content => {
return Err(DeriveError::SameTagAndContent);
}
(Some(tag), Some(content), None) => Some(VariantTag::Adjacent { tag, content }),
(None, Some(_), _) => return Err(DeriveError::ContentWithoutTag),
_ => return Err(DeriveError::ConflictingTagAttributes),
};
Ok(Self {
name,
root,
rename_all,
tag,
})
}
}
#[derive(Debug)]
struct NamedFieldInfo<'a> {
binding: &'a Ident,
key: String,
is_flattened: bool,
is_skipped: bool,
}
impl<'a> NamedFieldInfo<'a> {
fn new(container: ContainerInfo<'a>, f: &'a Field) -> syn::Result<Self> {
let name = f.ident.as_ref().unwrap();
let attrs: Vec<_> = f
.attrs
.iter()
.map(FieldAttr::parse_one)
.flatten_ok()
.try_collect()?;
let is_flattened = attrs.iter().any(|attr| matches!(attr, FieldAttr::Flatten));
let is_skipped = attrs.iter().any(|attr| matches!(attr, FieldAttr::Skip));
if is_flattened && is_skipped {
return Err(syn::Error::new_spanned(f, DeriveError::FlattenWithSkip));
}
let key = attrs
.iter()
.find_map(|attr| match attr {
FieldAttr::Rename(name) => Some(name.clone()),
_ => None,
})
.or_else(|| {
container
.rename_all
.map(|rename_all| rename_all.apply(&name.to_string()))
})
.unwrap_or_else(|| name.to_string());
Ok(NamedFieldInfo {
binding: name,
key,
is_flattened,
is_skipped,
})
}
}
#[derive(Debug)]
struct TupleFieldInfo {
index: usize,
binding: Ident,
is_skipped: bool,
}
impl TupleFieldInfo {
fn new(index: usize, f: &Field) -> syn::Result<Self> {
let attrs: Vec<_> = f
.attrs
.iter()
.map(FieldAttr::parse_one)
.flatten_ok()
.try_collect()?;
let _: () = attrs
.iter()
.map(|attr| match attr {
FieldAttr::Flatten => {
Err(syn::Error::new_spanned(f, DeriveError::FlattenOnNonNamed))
}
FieldAttr::Rename(_) => {
Err(syn::Error::new_spanned(f, DeriveError::RenameOnNonNamed))
}
_ => Ok(()),
})
.try_collect()?;
let is_skipped = attrs.iter().any(|attr| matches!(attr, FieldAttr::Skip));
Ok(Self {
index,
binding: format_ident!("f{}", index, span = Span::mixed_site()),
is_skipped,
})
}
}
#[derive(Clone, Copy)]
struct VariantInfo<'a> {
container: ContainerInfo<'a>,
name: &'a Ident,
attrs: &'a [VariantAttr],
}
impl<'a> VariantInfo<'a> {
fn new(container: ContainerInfo<'a>, name: &'a Ident, attrs: &'a [VariantAttr]) -> Self {
Self {
container,
name,
attrs,
}
}
fn effective_name(&self) -> String {
self.attrs
.iter()
.find_map(|attr| match attr {
VariantAttr::Rename(name) => Some(name.clone()),
_ => None,
})
.or_else(|| {
self.container
.rename_all
.map(|rename_all| rename_all.apply(&self.name.to_string()))
})
.unwrap_or_else(|| self.name.to_string())
}
fn is_skipped(&self) -> bool {
self.attrs
.iter()
.any(|attr| matches!(attr, VariantAttr::Skip))
}
}
#[derive(Clone, Copy)]
struct NamedPointeeBody<'a> {
ty: NamedPointeeTy<'a>,
pointer: &'a Ident,
fields: &'a [NamedFieldInfo<'a>],
}
impl<'a> NamedPointeeBody<'a> {
fn new(ty: NamedPointeeTy<'a>, pointer: &'a Ident, fields: &'a [NamedFieldInfo]) -> Self {
Self {
ty,
pointer,
fields,
}
}
}
impl ToTokens for NamedPointeeBody<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let root = self.ty.container().root;
let pointer = self.pointer;
let key = Ident::new("key", Span::mixed_site());
let pointee_ty = self.ty;
let arms = self
.fields
.iter()
.filter(|f| !f.is_flattened && !f.is_skipped)
.map(|f| {
let field_key = &f.key;
let binding = f.binding;
quote! {
#field_key => <_ as #root::JsonPointee>::resolve(
#binding,
#pointer.tail(),
)
}
});
let mut suggestions: Vec<_> = self
.fields
.iter()
.filter(|f| !f.is_flattened && !f.is_skipped)
.map(|f| {
let key = &f.key;
quote! { #key }
})
.collect();
if let NamedPointeeTy::Variant(_, VariantTag::Internal(tag)) = self.ty {
suggestions.push(quote! { #tag });
}
let wildcard = {
let rest = if cfg!(feature = "did-you-mean") {
quote!(Err(#root::JsonPointerKeyError::with_suggestions(
#key,
#pointee_ty,
[#(#suggestions),*],
))?)
} else {
quote!(Err(#root::JsonPointerKeyError::new(#key))?)
};
self.fields
.iter()
.filter(|f| f.is_flattened)
.rfold(rest, |rest, f| {
let binding = f.binding;
quote! {
<_ as #root::JsonPointee>
::resolve(
#binding,
#pointer
)
.or_else(|_| #rest)
}
})
};
let body = match self.ty {
NamedPointeeTy::Variant(info, VariantTag::Internal(tag_field)) => {
let variant_name = info.effective_name();
quote! {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
if #key == #tag_field {
return Ok(&#variant_name as &dyn #root::JsonPointee);
}
match &*#key.to_str() {
#(#arms,)*
_ => #wildcard,
}
}
}
NamedPointeeTy::Variant(info, VariantTag::External) => {
let variant_name = info.effective_name();
let ty_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #pointee_ty))
} else {
quote!(#root::JsonPointerTypeError::new(&#pointer))
};
quote! {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
if #key != #variant_name {
return Err(#ty_err)?;
}
let #pointer = #pointer.tail();
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
match &*#key.to_str() {
#(#arms,)*
_ => #wildcard,
}
}
}
NamedPointeeTy::Variant(
info,
VariantTag::Adjacent {
tag: tag_field,
content: content_field,
},
) => {
let variant_name = info.effective_name();
let key_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerKeyError::with_suggestions(
#key,
#pointee_ty,
[#tag_field, #content_field],
))
} else {
quote!(#root::JsonPointerKeyError::new(#key))
};
quote! {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
match &*#key.to_str() {
#tag_field => {
return Ok(&#variant_name as &dyn #root::JsonPointee);
}
#content_field => {
let #pointer = #pointer.tail();
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
match &*#key.to_str() {
#(#arms,)*
_ => #wildcard,
}
}
_ => {
return Err(#key_err)?;
}
}
}
}
NamedPointeeTy::Struct(_) | NamedPointeeTy::Variant(_, VariantTag::Untagged) => {
quote! {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
match &*#key.to_str() {
#(#arms,)*
_ => #wildcard,
}
}
}
};
tokens.append_all(body);
}
}
#[derive(Clone, Copy)]
struct TuplePointeeBody<'a> {
ty: TuplePointeeTy<'a>,
pointer: &'a Ident,
fields: &'a [TupleFieldInfo],
}
impl<'a> TuplePointeeBody<'a> {
fn new(ty: TuplePointeeTy<'a>, pointer: &'a Ident, fields: &'a [TupleFieldInfo]) -> Self {
Self {
ty,
pointer,
fields,
}
}
}
impl ToTokens for TuplePointeeBody<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let root = self.ty.container().root;
let pointer = self.pointer;
let idx = Ident::new("idx", Span::mixed_site());
let key = Ident::new("key", Span::mixed_site());
let arms = self.fields.iter().filter(|f| !f.is_skipped).map(|f| {
let index = f.index;
let binding = &f.binding;
quote! {
#index => <_ as #root::JsonPointee>::resolve(
#binding,
#pointer.tail(),
)
}
});
let ty = self.ty;
let len = self.fields.len();
let ty_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #ty))
} else {
quote!(#root::JsonPointerTypeError::new(&#pointer))
};
let tail = quote! {
let Some(#idx) = #key.to_index() else {
return Err(#ty_err)?;
};
match #idx {
#(#arms,)*
_ => Err(#root::JsonPointeeError::Index(#idx, 0..#len))
}
};
let body = match self.ty {
TuplePointeeTy::Variant(info, VariantTag::Internal(tag_field)) => {
let variant_name = info.effective_name();
quote! {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
if #key == #tag_field {
return Ok(&#variant_name as &dyn #root::JsonPointee);
}
#tail
}
}
TuplePointeeTy::Variant(info, VariantTag::External) => {
let variant_name = info.effective_name();
let ty_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #ty))
} else {
quote!(#root::JsonPointerTypeError::new(&#pointer))
};
quote! {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
if #key != #variant_name {
return Err(#ty_err)?;
}
let #pointer = #pointer.tail();
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
#tail
}
}
TuplePointeeTy::Variant(
info,
VariantTag::Adjacent {
tag: tag_field,
content: content_field,
},
) => {
let variant_name = info.effective_name();
let key_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerKeyError::with_suggestions(
#key,
#ty,
[#tag_field, #content_field],
))
} else {
quote!(#root::JsonPointerKeyError::new(#key))
};
quote! {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
match &*#key.to_str() {
#tag_field => {
return Ok(&#variant_name as &dyn #root::JsonPointee);
}
#content_field => {
let #pointer = #pointer.tail();
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
#tail
}
_ => {
return Err(#key_err)?;
}
}
}
}
TuplePointeeTy::Struct(_) | TuplePointeeTy::Variant(_, VariantTag::Untagged) => {
quote! {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
#tail
}
}
};
tokens.append_all(body);
}
}
#[derive(Clone, Copy)]
struct UnitPointeeBody<'a> {
ty: UnitPointeeTy<'a>,
pointer: &'a Ident,
}
impl<'a> UnitPointeeBody<'a> {
fn new(ty: UnitPointeeTy<'a>, pointer: &'a Ident) -> Self {
Self { ty, pointer }
}
}
impl ToTokens for UnitPointeeBody<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let root = self.ty.container().root;
let pointer = self.pointer;
let body = match self.ty {
ty @ UnitPointeeTy::Variant(info, VariantTag::Internal(tag_field)) => {
let key = Ident::new("key", Span::mixed_site());
let variant_name = info.effective_name();
let key_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerKeyError::with_suggestions(
#key,
#ty,
[#tag_field],
))
} else {
quote!(#root::JsonPointerKeyError::new(#key))
};
quote! {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
if #key == #tag_field {
return Ok(&#variant_name as &dyn #root::JsonPointee);
}
Err(#key_err)?
}
}
ty @ UnitPointeeTy::Variant(info, VariantTag::External) => {
let key = Ident::new("key", Span::mixed_site());
let variant_name = info.effective_name();
let key_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerKeyError::with_ty(#key, #ty))
} else {
quote!(#root::JsonPointerKeyError::new(#key))
};
let ty_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerTypeError::with_ty(&#pointer.tail(), #ty))
} else {
quote!(#root::JsonPointerTypeError::new(&#pointer.tail()))
};
quote! {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
if #key != #variant_name {
return Err(#key_err)?;
}
if !#pointer.tail().is_empty() {
return Err(#ty_err)?;
}
Ok(self as &dyn #root::JsonPointee)
}
}
ty @ UnitPointeeTy::Variant(info, VariantTag::Adjacent { tag: tag_field, .. }) => {
let key = Ident::new("key", Span::mixed_site());
let variant_name = info.effective_name();
let key_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerKeyError::with_suggestions(
#key,
#ty,
[#tag_field],
))
} else {
quote!(#root::JsonPointerKeyError::new(#key))
};
quote! {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
match &*#key.to_str() {
#tag_field => {
return Ok(&#variant_name as &dyn #root::JsonPointee);
}
_ => {
return Err(#key_err)?;
}
}
}
}
ty @ (UnitPointeeTy::Struct(_) | UnitPointeeTy::Variant(_, VariantTag::Untagged)) => {
let ty_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #ty))
} else {
quote!(#root::JsonPointerTypeError::new(&#pointer))
};
quote! {
if #pointer.is_empty() {
Ok(self as &dyn #root::JsonPointee)
} else {
Err(#ty_err)?
}
}
}
};
tokens.append_all(body);
}
}
#[derive(Clone, Copy)]
enum NamedPointeeTy<'a> {
Struct(ContainerInfo<'a>),
Variant(VariantInfo<'a>, VariantTag<'a>),
}
impl<'a> NamedPointeeTy<'a> {
fn container(self) -> ContainerInfo<'a> {
match self {
Self::Struct(info) => info,
Self::Variant(info, _) => info.container,
}
}
}
impl ToTokens for NamedPointeeTy<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let root = self.container().root;
tokens.append_all(match self {
Self::Struct(info) => {
let ty = info.name;
quote! {
#root::JsonPointeeType::struct_named(
stringify!(#ty)
)
}
}
Self::Variant(info, ..) => {
let ty = info.container.name;
let variant = info.name;
quote! {
#root::JsonPointeeType::struct_variant_named(
stringify!(#ty),
stringify!(#variant),
)
}
}
});
}
}
#[derive(Clone, Copy)]
enum TuplePointeeTy<'a> {
Struct(ContainerInfo<'a>),
Variant(VariantInfo<'a>, VariantTag<'a>),
}
impl<'a> TuplePointeeTy<'a> {
fn container(self) -> ContainerInfo<'a> {
match self {
Self::Struct(info) => info,
Self::Variant(info, _) => info.container,
}
}
}
impl ToTokens for TuplePointeeTy<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let root = self.container().root;
tokens.append_all(match self {
Self::Struct(info) => {
let ty = info.name;
quote! {
#root::JsonPointeeType::tuple_struct_named(
stringify!(#ty)
)
}
}
Self::Variant(info, ..) => {
let ty = info.container.name;
let variant = info.name;
quote! {
#root::JsonPointeeType::tuple_variant_named(
stringify!(#ty),
stringify!(#variant),
)
}
}
});
}
}
#[derive(Clone, Copy)]
enum UnitPointeeTy<'a> {
Struct(ContainerInfo<'a>),
Variant(VariantInfo<'a>, VariantTag<'a>),
}
impl<'a> UnitPointeeTy<'a> {
fn container(self) -> ContainerInfo<'a> {
match self {
Self::Struct(info) => info,
Self::Variant(info, _) => info.container,
}
}
}
impl ToTokens for UnitPointeeTy<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let root = self.container().root;
tokens.append_all(match self {
Self::Struct(info) => {
let ty = info.name;
quote! {
#root::JsonPointeeType::unit_struct_named(
stringify!(#ty)
)
}
}
Self::Variant(info, ..) => {
let ty = info.container.name;
let variant = info.name;
quote! {
#root::JsonPointeeType::unit_variant_named(
stringify!(#ty),
stringify!(#variant),
)
}
}
});
}
}
#[derive(Clone, Copy)]
enum VariantTy<'a> {
Named(VariantInfo<'a>, VariantTag<'a>),
Tuple(VariantInfo<'a>, VariantTag<'a>),
Unit(VariantInfo<'a>, VariantTag<'a>),
}
impl<'a> VariantTy<'a> {
fn info(self) -> VariantInfo<'a> {
let (Self::Named(info, _) | Self::Tuple(info, _) | Self::Unit(info, _)) = self;
info
}
fn tag(self) -> VariantTag<'a> {
let (Self::Named(_, tag) | Self::Tuple(_, tag) | Self::Unit(_, tag)) = self;
tag
}
}
impl ToTokens for VariantTy<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let root = self.info().container.root;
tokens.append_all(match self {
Self::Named(info, _) => {
let ty = info.container.name;
let variant = info.name;
quote! {
#root::JsonPointeeType::struct_variant_named(
stringify!(#ty),
stringify!(#variant),
)
}
}
Self::Tuple(info, _) => {
let ty = info.container.name;
let variant = info.name;
quote! {
#root::JsonPointeeType::tuple_variant_named(
stringify!(#ty),
stringify!(#variant),
)
}
}
Self::Unit(info, _) => {
let ty = info.container.name;
let variant = info.name;
quote! {
#root::JsonPointeeType::unit_variant_named(
stringify!(#ty),
stringify!(#variant),
)
}
}
});
}
}
#[derive(Clone, Copy)]
struct SkippedVariantBody<'a> {
ty: VariantTy<'a>,
pointer: &'a Ident,
}
impl<'a> SkippedVariantBody<'a> {
fn new(ty: VariantTy<'a>, pointer: &'a Ident) -> Self {
Self { ty, pointer }
}
}
impl ToTokens for SkippedVariantBody<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let root = self.ty.info().container.root;
let pointer = self.pointer;
let ty = self.ty;
let pattern = match ty {
VariantTy::Named(info, _) => {
let variant_name = info.name;
quote!(Self::#variant_name { .. })
}
VariantTy::Tuple(info, _) => {
let variant_name = info.name;
quote!(Self::#variant_name(..))
}
VariantTy::Unit(info, _) => {
let variant_name = info.name;
quote!(Self::#variant_name)
}
};
match ty.tag() {
VariantTag::Internal(tag_field) => {
let key = Ident::new("key", Span::mixed_site());
let effective_name = ty.info().effective_name();
let ty_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #ty))
} else {
quote!(#root::JsonPointerTypeError::new(&#pointer))
};
tokens.append_all(quote! {
#pattern => {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
if #key == #tag_field {
return Ok(&#effective_name as &dyn #root::JsonPointee);
}
Err(#ty_err)?
}
});
}
VariantTag::External => {
let ty_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #ty))
} else {
quote!(#root::JsonPointerTypeError::new(&#pointer))
};
tokens.append_all(quote! {
#pattern => Err(#ty_err)?
});
}
VariantTag::Adjacent { tag: tag_field, .. } => {
let key = Ident::new("key", Span::mixed_site());
let effective_name = ty.info().effective_name();
let key_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerKeyError::with_suggestions(
#key,
#ty,
[#tag_field],
))
} else {
quote!(#root::JsonPointerKeyError::new(#key))
};
tokens.append_all(quote! {
#pattern => {
let Some(#key) = #pointer.head() else {
return Ok(self as &dyn #root::JsonPointee);
};
match &*#key.to_str() {
#tag_field => {
return Ok(&#effective_name as &dyn #root::JsonPointee);
}
_ => {
return Err(#key_err)?;
}
}
}
});
}
VariantTag::Untagged => {
let ty_err = if cfg!(feature = "did-you-mean") {
quote!(#root::JsonPointerTypeError::with_ty(&#pointer, #ty))
} else {
quote!(#root::JsonPointerTypeError::new(&#pointer))
};
tokens.append_all(quote! {
#pattern => Err(#ty_err)?
});
}
}
}
}
#[derive(Clone, Copy, Debug)]
enum VariantTag<'a> {
Internal(&'a str),
External,
Adjacent { tag: &'a str, content: &'a str },
Untagged,
}
#[derive(Clone)]
enum ContainerAttr {
Crate(syn::Path),
RenameAll(RenameAll),
Tag(String),
Content(String),
Untagged,
}
impl ContainerAttr {
fn parse_all(attrs: &[Attribute]) -> syn::Result<Vec<Self>> {
attrs
.iter()
.map(ContainerAttr::parse_one)
.flatten_ok()
.try_collect()
}
fn parse_one(attr: &Attribute) -> syn::Result<Vec<Self>> {
if !attr.path().is_ident("ploidy") {
return Ok(vec![]);
}
let mut attrs = vec![];
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("pointer") {
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("crate") {
let value = meta.value()?;
let s: syn::LitStr = value.parse()?;
attrs.push(Self::Crate(s.parse()?));
} else if meta.path.is_ident("rename_all") {
let value = meta.value()?;
let s: syn::LitStr = value.parse()?;
let Some(rename) = RenameAll::from_str(&s.value()) else {
return Err(meta.error(DeriveError::BadRenameAll));
};
attrs.push(Self::RenameAll(rename));
} else if meta.path.is_ident("tag") {
let value = meta.value()?;
let s: syn::LitStr = value.parse()?;
attrs.push(Self::Tag(s.value()));
} else if meta.path.is_ident("content") {
let value = meta.value()?;
let s: syn::LitStr = value.parse()?;
attrs.push(Self::Content(s.value()));
} else if meta.path.is_ident("untagged") {
attrs.push(Self::Untagged);
} else {
return Err(meta.error(DeriveError::UnrecognizedPointer));
}
Ok(())
})?;
} else {
return Err(meta.error(DeriveError::UnrecognizedPloidy));
}
Ok(())
})?;
Ok(attrs)
}
}
#[derive(Clone, Debug)]
enum FieldAttr {
Rename(String),
Flatten,
Skip,
}
impl FieldAttr {
fn parse_one(attr: &Attribute) -> syn::Result<Vec<Self>> {
if !attr.path().is_ident("ploidy") {
return Ok(vec![]);
}
let mut attrs = vec![];
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("pointer") {
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("rename") {
let value = meta.value()?;
let s: syn::LitStr = value.parse()?;
attrs.push(Self::Rename(s.value()));
} else if meta.path.is_ident("flatten") {
attrs.push(Self::Flatten);
} else if meta.path.is_ident("skip") {
attrs.push(Self::Skip);
} else {
return Err(meta.error(DeriveError::UnrecognizedPointer));
}
Ok(())
})?;
} else {
return Err(meta.error(DeriveError::UnrecognizedPloidy));
}
Ok(())
})?;
Ok(attrs)
}
}
#[derive(Clone, Debug)]
enum VariantAttr {
Skip,
Rename(String),
}
impl VariantAttr {
fn parse_one(attr: &Attribute) -> syn::Result<Vec<Self>> {
if !attr.path().is_ident("ploidy") {
return Ok(vec![]);
}
let mut attrs = vec![];
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("pointer") {
meta.parse_nested_meta(|meta| {
if meta.path.is_ident("skip") {
attrs.push(Self::Skip);
} else if meta.path.is_ident("rename") {
let value = meta.value()?;
let s: syn::LitStr = value.parse()?;
attrs.push(Self::Rename(s.value()));
} else {
return Err(meta.error(DeriveError::UnrecognizedPointer));
}
Ok(())
})?;
} else {
return Err(meta.error(DeriveError::UnrecognizedPloidy));
}
Ok(())
})?;
Ok(attrs)
}
}
#[derive(Clone, Copy, Debug)]
enum RenameAll {
Lowercase,
Uppercase,
PascalCase,
CamelCase,
SnakeCase,
ScreamingSnakeCase,
KebabCase,
ScreamingKebabCase,
}
impl RenameAll {
const fn all() -> &'static [Self] {
&[
Self::Lowercase,
Self::Uppercase,
Self::PascalCase,
Self::CamelCase,
Self::SnakeCase,
Self::ScreamingSnakeCase,
Self::KebabCase,
Self::ScreamingKebabCase,
]
}
fn from_str(s: &str) -> Option<Self> {
Some(match s {
"lowercase" => RenameAll::Lowercase,
"UPPERCASE" => RenameAll::Uppercase,
"PascalCase" => RenameAll::PascalCase,
"camelCase" => RenameAll::CamelCase,
"snake_case" => RenameAll::SnakeCase,
"SCREAMING_SNAKE_CASE" => RenameAll::ScreamingSnakeCase,
"kebab-case" => RenameAll::KebabCase,
"SCREAMING-KEBAB-CASE" => RenameAll::ScreamingKebabCase,
_ => return None,
})
}
fn apply(&self, s: &str) -> String {
match self {
RenameAll::Lowercase => s.to_lowercase(),
RenameAll::Uppercase => s.to_uppercase(),
RenameAll::PascalCase => s.to_pascal_case(),
RenameAll::CamelCase => s.to_lower_camel_case(),
RenameAll::SnakeCase => s.to_snake_case(),
RenameAll::ScreamingSnakeCase => s.to_shouty_snake_case(),
RenameAll::KebabCase => s.to_kebab_case(),
RenameAll::ScreamingKebabCase => s.to_shouty_kebab_case(),
}
}
}
impl Display for RenameAll {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Lowercase => "lowercase",
Self::Uppercase => "UPPERCASE",
Self::PascalCase => "PascalCase",
Self::CamelCase => "camelCase",
Self::SnakeCase => "snake_case",
Self::ScreamingSnakeCase => "SCREAMING_SNAKE_CASE",
Self::KebabCase => "kebab-case",
Self::ScreamingKebabCase => "SCREAMING-KEBAB-CASE",
})
}
}
#[derive(Debug, thiserror::Error)]
enum DeriveError {
#[error("`JsonPointee` can't be derived for unions")]
Union,
#[error("`rename` is only supported on struct and struct-like enum variant fields")]
RenameOnNonNamed,
#[error("`flatten` is only supported on struct and struct-like enum variant fields")]
FlattenOnNonNamed,
#[error("`flatten` and `skip` are mutually exclusive")]
FlattenWithSkip,
#[error("`tag` is only supported on enums")]
TagOnNonEnum,
#[error("`content` requires `tag`")]
ContentWithoutTag,
#[error("`tag` and `content` must have different field names")]
SameTagAndContent,
#[error("only one of: `tag`, `tag` and `content`, `untagged` allowed")]
ConflictingTagAttributes,
#[error("`rename_all` must be one of: {}", RenameAll::all().iter().join(","))]
BadRenameAll,
#[error("unrecognized `#[ploidy(...)]` attribute")]
UnrecognizedPloidy,
#[error("unrecognized `#[ploidy(pointer(...))]` attribute")]
UnrecognizedPointer,
}