use proc_macro2::Span;
use quote::quote;
use syn::{parse_quote, parse_quote_spanned, spanned::Spanned};
use proc_macro::TokenStream;
use crate::derive_config::ConfigsForAsPartial;
use crate::syn_extensions::{IAttrExt, IEnumExt, IVariantExt};
use crate::serde_attributes::SerdeEnumTagParams;
fn where_clause_for_partial<'field>(
original_where: Option<syn::WhereClause>,
fields: impl IntoIterator<Item=&'field syn::Field>,
) ->syn::WhereClause {
let mut wc = original_where.unwrap_or(parse_quote!(where));
let comma = parse_quote!(,);
if !wc.predicates.empty_or_trailing() {
wc.predicates.push_punct(comma);
}
for field in fields.into_iter() {
let span = field.ty.span();
let field_ty = &field.ty;
wc.predicates.push_value( match cfg!(feature="serde") {
true => parse_quote_spanned!{ span=> #field_ty : ::aspartial::AsPartial<Partial: ::serde::de::DeserializeOwned> },
false => parse_quote_spanned!{ span=> #field_ty: ::aspartial::AsPartial },
});
wc.predicates.push_punct(comma);
if field.attrs.iter().any(|attr| attr.is_serde_regular_default()) {
let default_pred: syn::WherePredicate = parse_quote!(#field_ty: std::default::Default);
wc.predicates.push_value(default_pred);
wc.predicates.push_punct(comma);
}
}
wc
}
pub fn make_partial_enum(input: &syn::ItemEnum) -> syn::Result<TokenStream>{
let confs = ConfigsForAsPartial::from_attrs(&input.attrs)?;
let enum_tag_style = SerdeEnumTagParams::from_attributes(&input.attrs);
let (partial_struct_field_idents, variant_tags): (Vec<syn::Ident>, Vec<syn::LitStr>) = input.tagged_variants()
.map(|(tag, v)| (v.partial_field_name(), tag))
.unzip();
let empty_partial = quote!(Self{
#(#partial_struct_field_idents: None),*
});
let partial_from_value = quote!(Self{
#(#partial_struct_field_idents: ::serde_json::from_value(value.clone()).ok()),*
});
let partial_from_tag = quote!{
match tag.as_str(){
#(#variant_tags => Self{
#partial_struct_field_idents: ::serde_json::from_value(value.clone()).ok(),
.. #empty_partial
},)*
_ => #empty_partial
}
};
let partial_from_outer_tagged = quote! {
{ 'from_outer_tagged: {
let empty = #empty_partial;
#(if let Some(payload) = value.get(#variant_tags) {
break 'from_outer_tagged Self{
#partial_struct_field_idents: ::serde_json::from_value(payload.clone()).ok(),
.. empty
}
})*
break 'from_outer_tagged #partial_from_value
}}
};
let partial_type_ident = &confs.partial_type_ident;
let partial_type_attrs = confs.attrs;
let partial_struct_fields: Vec<syn::Field> = input.variants.iter()
.map(|v| v.as_partial_field())
.collect::<syn::Result<_>>()?;
let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
let fields = input.variants.iter()
.map(|v| v.fields())
.flatten();
let where_clause = where_clause_for_partial(input.generics.where_clause.clone(), fields);
let enum_ident = &input.ident;
let impl__TryFrom__json_value = cfg!(feature="serde").then_some(match enum_tag_style{
SerdeEnumTagParams::Untagged => quote!{
impl #impl_generics TryFrom<::serde_json::Value> for #partial_type_ident #ty_generics #where_clause {
type Error = ::serde_json::Error;
fn try_from(value: ::serde_json::Value) -> Result<Self, Self::Error> {
Ok(#partial_from_value)
}
}
},
SerdeEnumTagParams::InternallyTagged { tag_key } => quote!{
impl #impl_generics TryFrom<::serde_json::Value> for #partial_type_ident #ty_generics #where_clause {
type Error = ::serde_json::Error;
fn try_from(value: ::serde_json::Value) -> Result<Self, Self::Error> {
let tag = match value.get(#tag_key) {
Some(::serde_json::Value::String(tag)) => tag,
_ => return Ok(#partial_from_value),
};
Ok(#partial_from_tag)
}
}
},
SerdeEnumTagParams::AdjacentlyTagged { tag_key, content_key } => quote!{
impl #impl_generics TryFrom<::serde_json::Value> for #partial_type_ident #ty_generics #where_clause {
type Error = ::serde_json::Error;
fn try_from(value: ::serde_json::Value) -> Result<Self, Self::Error> {
let orig_val = &value;
let value = value.get(#content_key).unwrap_or(&value);
let tag = match orig_val.get(#tag_key) {
Some(::serde_json::Value::String(tag)) => tag,
_ => {
return Ok(#partial_from_value)
},
};
Ok(#partial_from_tag)
}
}
},
SerdeEnumTagParams::ExternallyTagged => quote! {
impl #impl_generics TryFrom<::serde_json::Value> for #partial_type_ident #ty_generics #where_clause {
type Error = ::serde_json::Error;
fn try_from(value: ::serde_json::Value) -> Result<Self, Self::Error> {
Ok(#partial_from_outer_tagged)
}
}
}
});
let partial_derive_deserialize = cfg!(feature="serde").then_some(quote!(
#[derive(::serde::Deserialize)]
#[serde(bound = "")]
#[serde(try_from="::serde_json::Value")]
));
let expanded = quote!{
impl #impl_generics ::aspartial::AsPartial for #enum_ident #ty_generics
#where_clause
{
type Partial = #partial_type_ident #impl_generics;
}
impl #impl_generics ::aspartial::AsPartial for #partial_type_ident #ty_generics
#where_clause
{
type Partial = #partial_type_ident #impl_generics;
}
#partial_derive_deserialize
#(#partial_type_attrs)*
pub struct #partial_type_ident #impl_generics
#where_clause
{
#(#partial_struct_fields),*
}
#impl__TryFrom__json_value
};
Ok(proc_macro::TokenStream::from(expanded))
}
pub fn make_partial_struct(input: &syn::ItemStruct) -> syn::Result<TokenStream>{
let confs = ConfigsForAsPartial::from_attrs(&input.attrs)?;
let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
let where_clause = where_clause_for_partial(
input.generics.where_clause.clone(),
input.fields.iter()
);
let partial_struct = {
let mut partial_struct = input.clone();
partial_struct.ident = confs.partial_type_ident;
partial_struct.attrs = confs.attrs;
if cfg!(feature="serde") {
partial_struct.attrs.push( parse_quote!( #[derive(::serde::Deserialize)] ));
partial_struct.attrs.push( parse_quote!( #[serde(bound = "")] ));
}
partial_struct.generics.where_clause = Some(where_clause.clone());
for field in partial_struct.fields.iter_mut() {
field.vis = parse_quote!(pub);
if cfg!(feature="serde") {
field.attrs.retain(|attr| attr.is_serde_attr());
let is_default_field = field.attrs.iter().any(|attr| attr.is_serde_any_default());
if !is_default_field{ let field_ty = &field.ty;
field.attrs.push(parse_quote!(#[serde(default)]));
field.ty = parse_quote!(Option< <#field_ty as ::aspartial::AsPartial>::Partial >);
}
} else {
field.attrs = vec![];
let field_ty = &field.ty;
field.ty = parse_quote!(Option< <#field_ty as ::aspartial::AsPartial>::Partial >);
}
}
partial_struct
};
let struct_name = &input.ident;
let partial_struct_name = &partial_struct.ident;
let expanded = quote! {
impl #impl_generics ::aspartial::AsPartial for #struct_name #ty_generics
#where_clause
{
type Partial = #partial_struct_name #impl_generics;
}
impl #impl_generics ::aspartial::AsPartial for #partial_struct_name #ty_generics
#where_clause
{
type Partial = Self;
}
#partial_struct
};
Ok(proc_macro::TokenStream::from(expanded))
}
pub fn do_derive_as_partial(input: TokenStream) -> syn::Result<TokenStream>{
let input = syn::parse::<syn::Item>(input)?;
let output = match input{
syn::Item::Struct(input_struct) => make_partial_struct(&input_struct),
syn::Item::Enum(input_enum) => make_partial_enum(&input_enum),
_ => return Err(syn::Error::new(Span::call_site(), "Must apply to enum or struct"))
}?;
Ok(proc_macro::TokenStream::from(output))
}