mod helper;
mod k8s;
mod parsed_input;
mod where_clause;
mod deepmerge;
mod optionable_map_keys_eq;
pub use deepmerge::derive_deepmerge;
mod map_keys_eq;
pub use map_keys_eq::derive_map_keys_eq;
use crate::helper::{destructure, error, error_on_helper_attributes, is_serialize, struct_wrapper};
use crate::k8s::{
ResourceType, error_missing_features, k8s_adjust_fields, k8s_openapi_derives,
k8s_openapi_impl_metadata, k8s_openapi_impl_resource, k8s_resource_type, k8s_type_attr,
};
use crate::optionable_map_keys_eq::optionable_map_keys_eq_impl;
use crate::parsed_input::{
FieldHandling, FieldParsed, StructParsed, StructType, into_field_handling,
};
use crate::where_clause::{WhereClauses, where_clauses};
use darling::util::PathList;
use darling::{FromAttributes, FromDeriveInput, FromMeta};
use itertools::MultiUnzip;
use proc_macro2::{Ident, Literal, TokenStream};
use quote::{ToTokens, format_ident, quote};
use std::borrow::Cow;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::default::Default;
use std::mem::take;
use syn::parse::Parser;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{
AttrStyle, Attribute, Data, DataEnum, DataStruct, DeriveInput, Error, Field, Fields,
GenericArgument, Generics, ImplGenerics, LitStr, Meta, MetaList, Path, PathArguments, Token,
Type, TypeGenerics, TypePath, WhereClause, parse_quote,
};
const HELPER_OPTIONABLE_IDENT: &str = "optionable";
const HELPER_ATTR_OPTIONABLE_IDENT: &str = "optionable_attr";
const ERR_MSG_HELPER_ATTR_ENUM_VARIANTS: &str =
"#[optionable] helper attributes not supported on enum variant level.";
#[derive(FromDeriveInput)]
#[darling(attributes(optionable))]
pub(crate) struct TypeHelperAttributes {
#[darling(multiple)]
attr_copy: Vec<FieldAttributeToCopy>,
#[darling(multiple)]
derive: Vec<PathList>,
suffix: Option<LitStr>,
no_convert: Option<()>,
option_wrap: Option<()>,
k8s_openapi: Option<TypeHelperAttributesK8sOpenapi>,
kube: Option<TypeHelperAttributesKube>,
}
#[derive(FromMeta)]
pub(crate) struct TypeHelperAttributesK8sOpenapi {
metadata: Option<()>,
resource: Option<()>,
}
#[derive(FromMeta)]
pub(crate) struct TypeHelperAttributesKube {
resource: Option<()>,
k8s_openapi_crate: Option<Path>,
kube_crate: Option<Path>,
}
#[derive(FromMeta)]
pub(crate) struct FieldAttributeToCopy {
pub attr: Path,
pub key: Option<Path>,
}
#[derive(FromMeta, Default, Clone, Debug)]
pub(crate) enum MergeBehaviour {
#[default]
OptionableConvert,
Atomic,
AppendNotPresent,
IterMap,
Ignore,
}
#[derive(FromAttributes)]
#[darling(attributes(optionable))]
struct FieldHelperAttributes {
required: Option<()>,
#[darling(rename = "optioned_type")]
optioned_ty: Option<Path>,
#[darling(default)]
merge: MergeBehaviour,
merge_map_key: Option<()>,
}
#[derive(FromDeriveInput, Debug, Clone)]
#[darling(attributes(optionable))]
pub struct CodegenSettings {
pub optionable_crate_name: Path,
pub ty_prefix: Option<Path>,
pub input_crate_replacement: Option<Ident>,
}
fn field_attr_copy_hashmap(
input: Vec<FieldAttributeToCopy>,
attr_kube: Option<&TypeHelperAttributesKube>,
) -> HashMap<Path, HashSet<Path>> {
let mut result = HashMap::<Path, HashSet<Path>>::new();
for el in input {
if let Some(key) = el.key {
if let Some(entry) = result.get_mut(&el.attr) {
entry.insert(key);
} else {
result.insert(el.attr, HashSet::from([key]));
}
}
}
if attr_kube.is_some() {
let path_serde = Path::from_string("serde").unwrap();
let path_rename = Path::from_string("rename").unwrap();
let path_rename_all = Path::from_string("rename_all").unwrap();
let path_rename_all_fields = Path::from_string("rename_all_fields").unwrap();
if let Some(entry) = result.get_mut(&path_serde) {
if !entry.is_empty() {
entry.insert(path_rename);
entry.insert(path_rename_all);
}
} else {
result.insert(
path_serde,
HashSet::from([path_rename, path_rename_all, path_rename_all_fields]),
);
}
}
result
}
impl Default for CodegenSettings {
fn default() -> Self {
Self {
optionable_crate_name: parse_quote!(::optionable),
ty_prefix: None,
input_crate_replacement: None,
}
}
}
#[must_use]
pub fn attribute_no_convert() -> Attribute {
parse_quote!(#[optionable(no_convert)])
}
#[must_use]
pub fn attribute_suffix(suffix: &str) -> Attribute {
parse_quote!(#[optionable(suffix=#suffix)])
}
#[must_use]
pub fn attribute_derives(derives: &PathList) -> Attribute {
parse_quote!(#[optionable(derive(#(#derives),*))])
}
struct Derived {
enum_struct: TokenStream,
fields: TokenStream,
where_clause_optionable: WhereClause,
where_clause_optionable_convert: Option<WhereClause>,
impl_optionable_convert: Option<TokenStream>,
}
struct DataProcessingContext<'a> {
crate_name: &'a Path,
input_crate_replacement: Option<&'a Ident>,
attr_option_wrap: Option<()>,
attr_no_convert: Option<()>,
attr_copy_identifier: HashMap<Path, HashSet<Path>>,
attr_k8s_openapi: Option<&'a TypeHelperAttributesK8sOpenapi>,
attr_kube: Option<&'a TypeHelperAttributesKube>,
k8s_resource_type: Option<ResourceType>,
generics: &'a Generics,
impl_generics: &'a ImplGenerics<'a>,
ty_generics: &'a TypeGenerics<'a>,
where_clause: Option<WhereClause>,
derive: &'a BTreeSet<String>,
skip_serde_if: Option<TokenStream>,
ty_ident: &'a Path,
ty_ident_opt: &'a Ident,
}
#[allow(clippy::too_many_lines)]
pub fn derive_optionable(
input: DeriveInput,
settings: Option<&CodegenSettings>,
) -> syn::Result<TokenStream> {
let settings = settings.map(Cow::Borrowed).unwrap_or_default();
let crate_name = &settings.optionable_crate_name;
let TypeHelperAttributes {
attr_copy,
derive: attr_derive,
suffix: attr_suffix,
no_convert: attr_no_convert,
option_wrap: attr_option_wrap,
k8s_openapi: attr_k8s_openapi,
kube: attr_kube,
} = TypeHelperAttributes::from_derive_input(&input)?;
let DeriveInput {
attrs,
vis,
ident,
mut generics,
data,
} = input;
error_missing_features(attr_k8s_openapi.as_ref(), attr_kube.as_ref())?;
let ty_ident_opt = {
let suffix = attr_suffix.map_or_else(
|| {
if attr_kube.is_some() || attr_k8s_openapi.is_some() {
Cow::Borrowed("Ac")
} else {
Cow::Borrowed("Opt")
}
},
|s| Cow::Owned(s.value()),
);
format_ident!("{}{suffix}", &ident)
};
let ty_ident = if let Some(mut ty_prefix) = settings.ty_prefix.clone() {
ty_prefix.segments.push(ident.into());
ty_prefix
} else {
ident.into()
};
if let Data::Enum(e) = &data
&& e.variants
.iter()
.all(|el| matches!(el.fields, Fields::Unit))
{
return Ok(impl_optionable_self(
crate_name,
&ty_ident,
attr_no_convert.is_some(),
));
}
let mut derive = attr_derive
.into_iter()
.flat_map(|el| {
el.iter()
.map(|el| el.to_token_stream().to_string())
.collect::<Vec<_>>()
})
.collect::<BTreeSet<_>>();
if attr_k8s_openapi.is_some()
&& let Some(k8s_openapi_derives) = k8s_openapi_derives(&data)
{
for el in k8s_openapi_derives {
derive.insert(el);
}
}
let k8s_resource_type =
k8s_resource_type(attr_k8s_openapi.as_ref(), attr_kube.as_ref(), &derive)?;
let k8s_openapi_attrs = attr_k8s_openapi.is_some().then(|| k8s_type_attr(&data));
let attr_copy_identifier = field_attr_copy_hashmap(attr_copy, attr_kube.as_ref());
let ty_attr_forwarded = forwarded_attributes(&attrs, &attr_copy_identifier)?;
let skip_serde_if = (attr_k8s_openapi.is_some() || derive.iter().any(|el| is_serialize(el.as_str())))
.then(|| quote!(#[serde(skip_serializing_if = "Option::is_none")]));
let where_clause = take(&mut generics.where_clause);
let (impl_generics, ty_generics, _) = generics.split_for_impl();
let ctx = DataProcessingContext {
crate_name,
input_crate_replacement: settings.input_crate_replacement.as_ref(),
attr_option_wrap,
attr_no_convert,
attr_copy_identifier,
attr_k8s_openapi: attr_k8s_openapi.as_ref(),
attr_kube: attr_kube.as_ref(),
k8s_resource_type,
generics: &generics,
impl_generics: &impl_generics,
ty_generics: &ty_generics,
where_clause,
derive: &derive,
skip_serde_if,
ty_ident: &ty_ident,
ty_ident_opt: &ty_ident_opt,
};
let Derived {
enum_struct,
fields,
where_clause_optionable,
where_clause_optionable_convert,
impl_optionable_convert,
} = match data {
Data::Struct(s) => process_struct_data(ctx, s)?,
Data::Enum(e) => process_enum_data(ctx, e)?,
Data::Union(_) => return error("#[derive(Optionable)] not supported for unit structs"),
};
let impl_optioned_convert = attr_no_convert.is_none().then(|| {
quote! {
#[automatically_derived]
impl #impl_generics #crate_name::OptionedConvert<#ty_ident #ty_generics> for #ty_ident_opt #ty_generics #where_clause_optionable_convert {
fn from_optionable(value: #ty_ident #ty_generics) -> Self {
#crate_name::OptionableConvert::into_optioned(value)
}
fn try_into_optionable(self) -> Result<#ty_ident #ty_generics, #crate_name::Error> {
#crate_name::OptionableConvert::try_from_optioned(self)
}
fn merge_into(self, other: &mut #ty_ident #ty_generics) -> Result<(), #crate_name::Error> {
#crate_name::OptionableConvert::merge(other, self)
}
}
}
});
let derive = derive
.into_iter()
.map(|derive| Path::from_string(&derive))
.collect::<Result<Vec<_>, _>>()?;
let derive = (!derive.is_empty()).then(|| quote! {#[derive(#(#derive),*)]});
let k8s_openapi_impl_resource = attr_k8s_openapi
.as_ref()
.is_some_and(|attr| attr.resource.is_some())
.then(|| {
k8s_openapi_impl_resource(
settings.input_crate_replacement.as_ref(),
&ty_ident,
&ty_ident_opt,
&impl_generics,
&ty_generics,
&where_clause_optionable,
)
});
let impl_k8s_metadata = attr_k8s_openapi
.as_ref()
.is_some_and(|attr| attr.metadata.is_some())
.then(|| {
k8s_openapi_impl_metadata(
settings.input_crate_replacement.as_ref(),
&ty_ident,
&ty_ident_opt,
&impl_generics,
&ty_generics,
&where_clause_optionable,
)
});
let kube_derive_resource = attr_kube.map(|attr| {
attr.resource?;
let kube_crate=attr.kube_crate.map(|kube_crate|kube_crate.to_token_stream().to_string());
let kube_resource =
Path::from_string(&format!("{}::Resource", kube_crate.as_deref().unwrap_or("::kube"))).unwrap();
let kube_crate =kube_crate.map(|mut kube_crate| {kube_crate.push_str("::core");kube_crate}).into_iter();
let k8s_openapi_crate=attr.k8s_openapi_crate.map(|k8s_openapi_crate| k8s_openapi_crate.to_token_stream().to_string()).into_iter();
Some(quote! {
#[derive(#kube_resource)]
#[resource(inherit = #ty_ident, crates(#(kube_core=#kube_crate,)* #(k8s_openapi=#k8s_openapi_crate,)*))]
})
});
let k8s_roundtrip_test = (attr_k8s_openapi.is_some_and(|attr|attr.resource.is_some())
&& ty_generics.to_token_stream().is_empty()
&& ty_ident_opt!="CustomResourceDefinitionAc"
&& ty_ident_opt!="StorageVersionAc"
&& ty_ident_opt!="DeviceClassAc"
&& ty_ident_opt!="ResourceClaimAc"
&& ty_ident_opt!="ResourceClaimTemplateAc")
.then(|| {
let fn_name = format_ident!(
"roundtrip_{}",
ty_ident_opt.to_token_stream().to_string().to_lowercase()
);
Ok::<_, Error>(quote! {
#[cfg(test_k8s_openapi_roundtrip)]
#[test]
fn #fn_name() {
crate::testutil::roundtrip_test::<#ty_ident>();
}
})
})
.transpose()?;
Ok(quote! {
#derive
#kube_derive_resource
#ty_attr_forwarded
#k8s_openapi_attrs
#vis #enum_struct #ty_ident_opt #impl_generics #where_clause_optionable #fields
#[automatically_derived]
impl #impl_generics #crate_name::Optionable for #ty_ident #ty_generics #where_clause_optionable {
type Optioned = #ty_ident_opt #ty_generics;
}
#[automatically_derived]
impl #impl_generics #crate_name::Optionable for #ty_ident_opt #ty_generics #where_clause_optionable {
type Optioned = #ty_ident_opt #ty_generics;
}
#impl_optionable_convert
#impl_optioned_convert
#k8s_openapi_impl_resource
#impl_k8s_metadata
#k8s_roundtrip_test
})
}
fn impl_optionable_self(crate_name: &Path, ty_ident: &Path, no_convert: bool) -> TokenStream {
let convert_impl = (!no_convert).then(|| {
quote! {
#[automatically_derived]
impl #crate_name::OptionableConvert for #ty_ident{
fn into_optioned(self) -> #ty_ident {
self
}
fn try_from_optioned(value: Self::Optioned) -> Result<Self, #crate_name::Error> {
Ok(value)
}
fn merge(&mut self, other: Self::Optioned) -> Result<(), #crate_name::Error> {
*self = other;
Ok(())
}
}
}
});
quote! {
#[automatically_derived]
impl #crate_name::Optionable for #ty_ident{
type Optioned = Self;
}
#convert_impl
}
}
#[allow(clippy::too_many_lines)]
fn process_struct_data(
ctx: DataProcessingContext<'_>,
data_struct: DataStruct,
) -> syn::Result<Derived> {
let impl_generics = ctx.impl_generics;
let ty_generics = ctx.ty_generics;
let generics_colon = (!ctx.generics.params.is_empty()).then(|| quote! {::});
let mut struct_parsed = into_field_handling(
ctx.crate_name.to_owned(),
data_struct.fields,
ctx.input_crate_replacement,
ctx.attr_option_wrap,
)?;
k8s_adjust_fields(
ctx.derive,
&mut struct_parsed,
ctx.attr_k8s_openapi,
ctx.attr_kube,
ctx.k8s_resource_type.as_ref(),
ctx.crate_name,
)?;
let WhereClauses {
optionable: where_clause_optionable,
optionable_convert: where_clause_optionable_convert,
} = where_clauses(
ctx.where_clause,
&ctx.generics.params,
ctx.crate_name,
ctx.input_crate_replacement,
ctx.derive,
ctx.attr_no_convert.is_some(),
&struct_parsed.fields,
)?;
let unnamed_struct_semicolon =
(struct_parsed.struct_type == StructType::Unnamed).then(|| quote!(;));
let optioned_fields = optioned_fields(
&struct_parsed,
ctx.skip_serde_if.as_ref(),
&ctx.attr_copy_identifier,
)?;
let crate_name = ctx.crate_name;
let ty_ident = ctx.ty_ident;
let ty_ident_opt = ctx.ty_ident_opt;
let impl_optionable_convert = ctx.attr_no_convert.is_none().then(|| {
let map_keys_eq_impl = optionable_map_keys_eq_impl(&struct_parsed.fields);
let into_optioned_fields =
into_optioned(&struct_parsed, |selector| quote! { self.#selector });
let try_from_optioned_fields =
try_from_optioned(&struct_parsed, |selector| quote! { value.#selector });
let merge_fields = merge_fields(
&struct_parsed,
|selector| quote! { self.#selector },
|selector| quote! { other.#selector },
true,
)?;
let map_keys_eq_impl = map_keys_eq_impl.map(|map_keys_eq_impl|quote! {
#[automatically_derived]
impl #impl_generics #crate_name::merge::OptionableMapKeysEq for #ty_ident #ty_generics #where_clause_optionable_convert {
fn keys_eq(&self, other: &<Self as #crate_name::Optionable>::Optioned) -> bool {
#map_keys_eq_impl
}
}
});
Ok::<_,Error>(quote! {
#[automatically_derived]
impl #impl_generics #crate_name::OptionableConvert for #ty_ident #ty_generics #where_clause_optionable_convert {
fn into_optioned(self) -> #ty_ident_opt #ty_generics {
#ty_ident_opt #generics_colon #ty_generics #into_optioned_fields
}
fn try_from_optioned(value: #ty_ident_opt #ty_generics) -> Result<Self, #crate_name::Error> {
Ok(Self #try_from_optioned_fields)
}
fn merge(&mut self, other: #ty_ident_opt #ty_generics) -> Result<(), #crate_name::Error> {
#merge_fields
Ok(())
}
}
#map_keys_eq_impl
})
}).transpose()?;
Ok(Derived {
enum_struct: quote! {struct},
fields: quote! {#optioned_fields #unnamed_struct_semicolon},
where_clause_optionable,
where_clause_optionable_convert,
impl_optionable_convert,
})
}
#[allow(clippy::too_many_lines)]
fn process_enum_data(ctx: DataProcessingContext<'_>, data_enum: DataEnum) -> syn::Result<Derived> {
let self_prefix = quote! {self_};
let other_prefix = quote! {other_};
let impl_generics = ctx.impl_generics;
let ty_generics = ctx.ty_generics;
let crate_name = ctx.crate_name;
let ty_ident = ctx.ty_ident;
let ty_ident_opt = ctx.ty_ident_opt;
let variants = data_enum
.variants
.into_iter()
.map(|v| {
error_on_helper_attributes(&v.attrs, ERR_MSG_HELPER_ATTR_ENUM_VARIANTS)?;
let mut field_handling = into_field_handling(
ctx.crate_name.to_owned(),
v.fields,
ctx.input_crate_replacement,
ctx.attr_option_wrap,
)?;
k8s_adjust_fields(
ctx.derive,
&mut field_handling,
ctx.attr_k8s_openapi,
ctx.attr_kube,
ctx.k8s_resource_type.as_ref(),
ctx.crate_name,
)?;
Ok::<_, Error>((
v.ident,
forwarded_attributes(&v.attrs, &ctx.attr_copy_identifier)?,
field_handling,
))
})
.collect::<Result<Vec<_>, _>>()?;
let all_fields = variants
.iter()
.flat_map(|(_, _, fields)| &fields.fields)
.collect::<Vec<_>>();
let WhereClauses {
optionable: where_clause_optionable,
optionable_convert: where_clause_optionable_convert,
} = where_clauses(
ctx.where_clause,
&ctx.generics.params,
ctx.crate_name,
ctx.input_crate_replacement,
ctx.derive,
ctx.attr_no_convert.is_some(),
all_fields,
)?;
let optioned_variants = variants
.iter()
.map(|(variant, forward_attrs, f)| {
let fields = optioned_fields(f, ctx.skip_serde_if.as_ref(), &ctx.attr_copy_identifier)?;
Ok::<_, Error>(quote!( #forward_attrs #variant #fields ))
})
.collect::<Result<Vec<_>, _>>()?;
let impl_optionable_convert = ctx
.attr_no_convert
.is_none()
.then(|| {
let (into_variants, try_from_variants, merge_variants): (Vec<_>, Vec<_>, Vec<_>) =
variants
.iter()
.map(|(variant, _, struct_parsed)| {
let fields_into = into_optioned(struct_parsed, |selector| {
format_ident!("{self_prefix}{selector}").to_token_stream()
});
let fields_try_from = try_from_optioned(struct_parsed, |selector| {
format_ident!("{other_prefix}{selector}").to_token_stream()
});
let fields_merge = merge_fields(
struct_parsed,
|selector| format_ident!("{self_prefix}{selector}").to_token_stream(),
|selector| format_ident!("{other_prefix}{selector}").to_token_stream(),
false,
)?;
let self_destructure = destructure(struct_parsed, &self_prefix)?;
let other_destructure = destructure(struct_parsed, &other_prefix)?;
Ok::<_, Error>((
quote!( Self::#variant #self_destructure => #ty_ident_opt::#variant #fields_into ),
quote!( #ty_ident_opt::#variant #other_destructure => Self::#variant #fields_try_from ),
quote!( #ty_ident_opt::#variant #other_destructure => {
if let Self::#variant #self_destructure = self {
#fields_merge
} else {
*self = Self::try_from_optioned(#ty_ident_opt::#variant #other_destructure)?;
}
}),
))
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.multiunzip();
Ok::<_, Error>(quote! {
#[automatically_derived]
impl #impl_generics #crate_name::OptionableConvert for #ty_ident #ty_generics #where_clause_optionable_convert {
fn into_optioned(self) -> #ty_ident_opt #ty_generics {
match self {
#(#into_variants),*
}
}
fn try_from_optioned(other: #ty_ident_opt #ty_generics) -> Result<Self, #crate_name::Error> {
Ok(match other {
#(#try_from_variants),*
})
}
fn merge(&mut self, other: #ty_ident_opt #ty_generics) -> Result<(), #crate_name::Error> {
match other {
#(#merge_variants),*
}
Ok(())
}
}
})
})
.transpose()?;
Ok(Derived {
enum_struct: quote! {enum},
fields: quote! {{#(#optioned_variants),*}},
where_clause_optionable,
where_clause_optionable_convert,
impl_optionable_convert,
})
}
fn optioned_fields(
fields: &StructParsed,
skip_serde_if: Option<&TokenStream>,
field_attr_forwards: &HashMap<Path, HashSet<Path>>,
) -> Result<TokenStream, Error> {
let fields_token = fields.fields.iter().map(
|FieldParsed {
field: Field { attrs, vis, ident, ty, .. },
handling,
merge_behaviour:_,
is_option,
}| {
let forwarded_attrs = forwarded_attributes(attrs, field_attr_forwards)?;
let optioned_ty = optioned_ty(&fields.crate_name, ty);
let colon = ident.as_ref().map(|_| quote! {:});
Ok::<_, Error>(match (handling,is_option) {
(FieldHandling::MapKey | FieldHandling::Required,false) | (FieldHandling::OptionedOnly,_) => quote! {#forwarded_attrs #vis #ident #colon #ty},
(FieldHandling::MapKey|FieldHandling::Required,true) => quote! {#forwarded_attrs #skip_serde_if #vis #ident #colon #ty},
(FieldHandling::ManualOptioned(ty_opt),_) => quote! {#forwarded_attrs #vis #ident #colon Option<#ty_opt>},
(FieldHandling::Other,true) => quote! {#forwarded_attrs #skip_serde_if #vis #ident #colon #optioned_ty},
(FieldHandling::Other,false) => quote! {#forwarded_attrs #skip_serde_if #vis #ident #colon Option<#optioned_ty>},
})
},
).collect::<Result<Vec<_>, _>>()?;
Ok(struct_wrapper(fields_token, &fields.struct_type))
}
fn into_optioned(
struct_parsed: &StructParsed,
self_selector_fn: impl Fn(&TokenStream) -> TokenStream,
) -> TokenStream {
let fields_token = struct_parsed.fields.iter().enumerate().map(|(i, FieldParsed { field: Field { ident, ty, .. }, handling, merge_behaviour: _,is_option})| {
let colon = ident.as_ref().map(|_| quote! {:});
let selector = ident.as_ref().map_or_else(|| {
let i = Literal::usize_unsuffixed(i);
quote! {#i}
}, ToTokens::to_token_stream);
let self_selector = self_selector_fn(&selector);
let crate_name = &struct_parsed.crate_name;
match (handling, is_self_resolving_optioned(ty),is_option) {
(FieldHandling::MapKey|FieldHandling::Required, _,_) | (_, true,true) => quote! {#ident #colon #self_selector},
(FieldHandling::ManualOptioned(_), _,_) => quote! {#ident #colon Some(#crate_name::OptionedConvert::from_optionable(#self_selector))},
(FieldHandling::Other, false,true) => quote! {#ident #colon #crate_name::OptionableConvert::into_optioned(#self_selector)},
(FieldHandling::Other, true,false) => quote! {#ident #colon Some(#self_selector)},
(FieldHandling::Other, false,false) => quote! {#ident #colon Some(#crate_name::OptionableConvert::into_optioned(#self_selector))},
(FieldHandling::OptionedOnly, _,_) => quote! {#ident #colon Default::default()},
}
});
struct_wrapper(fields_token, &struct_parsed.struct_type)
}
fn try_from_optioned(
struct_parsed: &StructParsed,
value_selector_fn: impl Fn(&TokenStream) -> TokenStream,
) -> TokenStream {
let fields_token = struct_parsed.fields.iter().enumerate().filter_map(|(i, FieldParsed { field: Field { ident, ty, .. }, handling, merge_behaviour:_,is_option})| {
let colon = ident.as_ref().map(|_| quote! {:});
let selector = ident.as_ref().map_or_else(|| {
let i = Literal::usize_unsuffixed(i);
quote! {#i}
}, ToTokens::to_token_stream);
let value_selector = value_selector_fn(&selector);
let crate_name = &struct_parsed.crate_name;
match (handling, is_self_resolving_optioned(ty),is_option) {
(FieldHandling::MapKey|FieldHandling::Required, _,_) | (_, true,true) => Some(quote! {#ident #colon value.#selector}),
(FieldHandling::ManualOptioned(_), _,_) => {
let selector_quoted = LitStr::new(&selector.to_string(), ident.span());
Some(quote! {
#ident #colon #crate_name::OptionedConvert::try_into_optionable(#value_selector.ok_or(#crate_name::Error{ missing_field: #selector_quoted })?
)?
})
}
(FieldHandling::Other, false,true) => Some(quote! {
#ident #colon #crate_name::OptionableConvert::try_from_optioned(
#value_selector
)?
}),
(FieldHandling::Other, true,false) => {
let selector_quoted = LitStr::new(&selector.to_string(), ident.span());
Some(quote! {
#ident #colon #value_selector.ok_or(#crate_name::Error{ missing_field: #selector_quoted })?
})
}
(FieldHandling::Other, false,_) => {
let selector_quoted = LitStr::new(&selector.to_string(), ident.span());
Some(quote! {
#ident #colon #crate_name::OptionableConvert::try_from_optioned(#value_selector.ok_or(#crate_name::Error{ missing_field: #selector_quoted })?
)?
})
}
(FieldHandling::OptionedOnly, _,_) => None,
}
});
struct_wrapper(fields_token, &struct_parsed.struct_type)
}
fn merge_fields(
struct_parsed: &StructParsed,
self_selector_fn: impl Fn(&TokenStream) -> TokenStream,
other_selector_fn: impl Fn(&TokenStream) -> TokenStream,
merge_self_mut: bool,
) -> Result<TokenStream, Error> {
let fields_token = struct_parsed.fields.iter().enumerate().filter_map(
|(
i,
FieldParsed {
field: Field { ident, ty, .. },
handling,
merge_behaviour,
is_option
},
)| {
let selector = ident.as_ref().map_or_else(
|| {
let i = Literal::usize_unsuffixed(i);
quote! {#i}
},
ToTokens::to_token_stream,
);
let self_merge_mut_modifier = merge_self_mut.then(|| quote! {&mut});
let deref_modifier = (!merge_self_mut).then(|| quote! {*});
let self_selector = self_selector_fn(&selector);
let other_selector = other_selector_fn(&selector);
let crate_name = &struct_parsed.crate_name;
let is_self_resolving_ty= is_self_resolving_optioned(ty);
let err=|| {Some(error(format!("Unsupported combination of custom merge behaviour `{merge_behaviour:?}` for the field `{}`. If you expect this specific case to work, pls open an issue for `optionable` with your use case and this information: handling={handling:?},self_resolving={is_self_resolving_ty},ty={}",ident.to_token_stream(),ty.to_token_stream())))};
let merge_fn=|self_merge_mut_modifier,deref_modifier,self_selector,other_selector|{
match merge_behaviour{
MergeBehaviour::OptionableConvert => {
quote!(
#crate_name::OptionableConvert::merge(#self_merge_mut_modifier #self_selector, #other_selector)?;
)
},
MergeBehaviour::Atomic => {
quote!(
#deref_modifier #self_selector = #crate_name::OptionableConvert::try_from_optioned(#other_selector)?;
)
},
MergeBehaviour::AppendNotPresent => {
quote!(
#crate_name::merge::try_merge_optioned_set(#self_merge_mut_modifier #self_selector, #other_selector)?;
)
},
MergeBehaviour::IterMap => {
quote!(
#crate_name::merge::try_merge_optioned_map(#self_merge_mut_modifier #self_selector, #other_selector)?;
)
}
MergeBehaviour::Ignore => quote!{},
}
};
match (handling,is_option) {
(FieldHandling::MapKey|FieldHandling::Required,_) => {
if !matches!(merge_behaviour,MergeBehaviour::OptionableConvert|MergeBehaviour::Atomic){return err()}
Some(Ok::<_,Error>(quote! {#deref_modifier #self_selector = #other_selector;}))
},
(_,true) => {
let merge=merge_fn(None::<TokenStream>.to_token_stream(),quote!(*),quote!(self_value),quote!(other_value));
Some(Ok(quote!{
if #self_selector.is_none(){
#deref_modifier #self_selector = #crate_name::OptionableConvert::try_from_optioned(#other_selector)?;
} else if let Some(self_value) = #self_selector.as_mut() && let Some(other_value) = #other_selector{
#merge
}
}))
},
(FieldHandling::ManualOptioned(_),_) => {
if !matches!(merge_behaviour,MergeBehaviour::OptionableConvert){return err()}
Some(Ok(quote! {
if let Some(other_value)=#other_selector{
#crate_name::OptionedConvert::merge_into(other_value, #self_merge_mut_modifier #self_selector)?;
}
}))
},
(FieldHandling::Other,_) => {
if is_self_resolving_ty && matches!(merge_behaviour,MergeBehaviour::OptionableConvert|MergeBehaviour::Atomic){
Some(Ok(quote!(
if let Some(other_value) = #other_selector{
#deref_modifier #self_selector = #crate_name::OptionableConvert::try_from_optioned(other_value)?;
}
)))
} else {
let merge=merge_fn(self_merge_mut_modifier.to_token_stream(),deref_modifier.to_token_stream(),self_selector,quote!(other_value));
Some(Ok(quote! {
if let Some(other_value)=#other_selector{
#merge
}
}))
}
},
(FieldHandling::OptionedOnly,_) =>None,
}
},
).collect::<Result<Vec<_>,_>>()?;
Ok(quote! {
#(#fields_token)*
})
}
#[must_use]
pub fn optioned_ty(crate_name: &Path, ty: &Type) -> TokenStream {
if is_self_resolving_optioned(ty) {
return ty.to_token_stream();
}
let self_resolving_container: Option<TokenStream> = 'c: {
let Some((ident, type_args)) = extract_self_resolving_ident(ty) else {
break 'c None;
};
let Some(type_args) = type_args else {
break 'c None;
};
let type_args_count = type_args.count();
let mut optioned_args_index = None;
if (SINGLE_TYPE_PARAM_CONTAINERS.contains(&ident.as_str()) && type_args_count == 1)
|| (ident == "Result" && type_args_count == 2)
{
optioned_args_index = Some(0);
} else if MAP_CONTAINERS.contains(&ident.as_str()) {
optioned_args_index = Some(1);
}
optioned_args_index.and_then(|i| {
let mut ty = ty.clone();
let Some((_ident, Some(type_args))) = extract_self_resolving_ident_mut(&mut ty) else {
return None;
};
let mut type_args = type_args.skip(i);
if let Some(inner_ty) = type_args.next() {
let transformed = optioned_ty(crate_name, inner_ty);
*inner_ty = parse_quote! {#transformed};
drop(type_args);
Some(ty.to_token_stream())
} else {
None
}
})
};
if let Some(ts) = self_resolving_container {
return ts;
}
quote! { <#ty as #crate_name::Optionable>::Optioned }
}
const SELF_RESOLVING_TYPES: [&str; 18] = [
"i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize", "f32",
"f64", "char", "bool", "String", "OsString",
];
const SINGLE_TYPE_PARAM_CONTAINERS: [&str; 13] = [
"Vec",
"VecDeque",
"LinkedList",
"BinaryHeap",
"BTreeSet",
"HashSet",
"Option",
"Box",
"Arc",
"Rc",
"Cell",
"RefCell",
"Cow",
];
const MAP_CONTAINERS: [&str; 2] = ["HashMap", "BTreeMap"];
fn is_self_resolving_optioned(ty: &Type) -> bool {
let Some((ident, type_args)) = extract_self_resolving_ident(ty) else {
return false;
};
if SELF_RESOLVING_TYPES.contains(&ident.as_str()) {
return true;
}
let Some(mut type_args) = type_args else {
return false;
};
if SINGLE_TYPE_PARAM_CONTAINERS.contains(&ident.as_str()) {
type_args.next().is_some_and(is_self_resolving_optioned)
} else if MAP_CONTAINERS.contains(&ident.as_str()) {
type_args.nth(1).is_some_and(is_self_resolving_optioned)
} else if ident == "Result" {
type_args.next().is_some_and(is_self_resolving_optioned)
} else {
false
}
}
fn extract_self_resolving_ident(
ty: &Type,
) -> Option<(String, Option<impl Iterator<Item = &Type>>)> {
let Type::Path(TypePath { qself, path }) = ty else {
return None;
};
if qself.is_some() {
return None;
}
let segment = path.segments.last()?;
let PathArguments::AngleBracketed(args) = &segment.arguments else {
return Some((segment.ident.to_string(), None));
};
let type_args = args.args.iter().filter_map(|arg| {
if let GenericArgument::Type(ty) = arg {
Some(ty)
} else {
None
}
});
Some((segment.ident.to_string(), Some(type_args)))
}
fn extract_self_resolving_ident_mut(
ty: &mut Type,
) -> Option<(String, Option<impl Iterator<Item = &mut Type>>)> {
let Type::Path(TypePath { qself, path }) = ty else {
return None;
};
if qself.is_some() {
return None;
}
let segment = path.segments.last_mut()?;
let PathArguments::AngleBracketed(args) = &mut segment.arguments else {
return Some((segment.ident.to_string(), None));
};
let type_args = args.args.iter_mut().filter_map(|arg| {
if let GenericArgument::Type(ty) = arg {
Some(ty)
} else {
None
}
});
Some((segment.ident.to_string(), Some(type_args)))
}
pub(crate) fn peel_optionable_container(ty: &Type) -> &Type {
let Some((ident, type_args)) = extract_self_resolving_ident(ty) else {
return ty;
};
let Some(mut type_args) = type_args else {
return ty;
};
let inner = if SINGLE_TYPE_PARAM_CONTAINERS.contains(&ident.as_str()) {
type_args.next()
} else if MAP_CONTAINERS.contains(&ident.as_str()) {
type_args.nth(1)
} else if ident == "Result" {
type_args.next()
} else {
None
};
match inner {
Some(inner_ty) => peel_optionable_container(inner_ty),
None => ty,
}
}
fn forwarded_attributes(
attrs: &[Attribute],
attr_to_copy: &HashMap<Path, HashSet<Path>>,
) -> Result<Option<TokenStream>, Error> {
let forward_attrs = attrs
.iter()
.map(|attr| {
if matches!(attr.style,AttrStyle::Inner(_)){
return Ok(None)
}
if attr.path().is_ident("doc"){
return Ok(Some(attr.to_token_stream()))
}
if attr.path().is_ident(HELPER_ATTR_OPTIONABLE_IDENT) {
return match &attr.meta {
Meta::List(MetaList { tokens, .. }) => Ok(Some(quote!(#[#tokens]))),
_ => error("Only lists like `#[optionable_attr(Serialize,Deserialize)]` are supported for `optionable_attr`"),
};
}
let keys_to_copy = attr_to_copy.get(attr.path());
if let Some(keys_to_copy) = keys_to_copy {
if keys_to_copy.is_empty() {
if attr.path().is_ident("derive") && let Meta::List(meta_list) = &attr.meta {
let derives = Punctuated::<Path, Token![,]>::parse_terminated
.parse2(meta_list.tokens.clone())?.into_iter().filter(|el| el.to_token_stream().to_string() != "Optionable" && el.to_token_stream().to_string() != "optionable::Optionable");
return Ok(Some(quote! {#[derive(#(#derives,)*)]}));
}
Ok(Some(attr.to_token_stream()))
} else {
match &attr.meta {
Meta::Path(_) => Ok(None),
Meta::NameValue(meta_name_value) => Ok(keys_to_copy.contains(&meta_name_value.path).then(|| attr.to_token_stream())),
Meta::List(meta_list) => {
let inner_metas: Vec<TokenStream> = Punctuated::<Meta, Token![,]>::parse_terminated
.parse2(meta_list.tokens.clone())?.into_iter().filter_map(|meta| {
if let Meta::NameValue(meta_name_value) = meta {
keys_to_copy.contains(&meta_name_value.path).then(|| Ok::<_, Error>(meta_name_value.to_token_stream()))
} else {
None
}
}).collect::<Result<_, _>>()?;
if inner_metas.is_empty() {
Ok(None)
} else {
let attr = Attribute {
meta: MetaList {
path: meta_list.path.clone(),
delimiter: meta_list.delimiter.clone(),
tokens: inner_metas.into_iter().collect(),
}.into(),
..*attr
};
Ok(Some(attr.to_token_stream()))
}
}
}
}
} else {
Ok(None)
}
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.flatten()
.collect::<TokenStream>();
Ok((!forward_attrs.is_empty()).then_some(forward_attrs))
}
#[cfg(test)]
mod tests {
use crate::{CodegenSettings, derive_optionable};
use darling::FromMeta;
use quote::quote;
use syn::{Path, parse_quote};
pub(crate) fn normalize_token_str(s: &str) -> String {
s.replace(">>", "> >")
}
fn assert_optionable(input: proc_macro2::TokenStream, expected: proc_macro2::TokenStream) {
let parsed = syn::parse2(input).unwrap();
let output = derive_optionable(parsed, None).unwrap();
assert_eq!(
normalize_token_str(&expected.to_string()),
normalize_token_str(&output.to_string())
);
}
#[test]
fn test_named_struct_fields() {
assert_optionable(
quote! {
#[derive(Optionable)]
struct DeriveExample {
name: String,
pub surname: String,
}
},
quote! {
struct DeriveExampleOpt {
name: Option<String>,
pub surname: Option<String>
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExample {
type Optioned = DeriveExampleOpt;
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExampleOpt {
type Optioned = DeriveExampleOpt;
}
#[automatically_derived]
impl ::optionable::OptionableConvert for DeriveExample {
fn into_optioned (self) -> DeriveExampleOpt {
DeriveExampleOpt {
name: Some(self.name),
surname:Some(self.surname)
}
}
fn try_from_optioned(value: DeriveExampleOpt ) -> Result <Self, ::optionable::Error> {
Ok(Self{
name: value.name.ok_or(::optionable::Error { missing_field: "name" })?,
surname: value.surname.ok_or(::optionable::Error { missing_field: "surname" })?
})
}
fn merge(&mut self, other: DeriveExampleOpt ) -> Result<(), ::optionable::Error> {
if let Some(other_value) = other.name {
self.name = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
if let Some(other_value) = other.surname {
self.surname = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
Ok(())
}
}
#[automatically_derived]
impl ::optionable::OptionedConvert<DeriveExample> for DeriveExampleOpt {
fn from_optionable(value: DeriveExample) -> Self {
::optionable::OptionableConvert::into_optioned(value)
}
fn try_into_optionable(self) -> Result<DeriveExample, ::optionable::Error> {
::optionable::OptionableConvert::try_from_optioned(self)
}
fn merge_into(self, other: &mut DeriveExample) -> Result<(), ::optionable::Error> {
::optionable::OptionableConvert::merge(other, self)
}
}
},
);
}
#[test]
fn test_named_struct_no_convert() {
assert_optionable(
quote! {
#[derive(Optionable)]
#[optionable(no_convert)]
struct DeriveExample {
name: String,
pub surname: String,
}
},
quote! {
struct DeriveExampleOpt {
name: Option<String>,
pub surname: Option<String>
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExample {
type Optioned = DeriveExampleOpt;
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExampleOpt {
type Optioned = DeriveExampleOpt;
}
},
);
}
#[test]
fn test_named_struct_required_fields() {
assert_optionable(
quote! {
#[derive(Optionable)]
struct DeriveExample {
name: String,
#[optionable(merge_map_key)]
pub surname: String,
#[optionable(required)]
pub surname2: String,
#[optionable(merge_map_key)]
pub surname3: String,
#[optionable(optioned_type=MyInt)]
pub id: i32,
}
},
quote! {
struct DeriveExampleOpt {
name: Option<String>,
pub surname: String,
pub surname2: String,
pub surname3: String,
pub id: Option<MyInt>
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExample {
type Optioned = DeriveExampleOpt;
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExampleOpt {
type Optioned = DeriveExampleOpt;
}
#[automatically_derived]
impl ::optionable::OptionableConvert for DeriveExample {
fn into_optioned (self) -> DeriveExampleOpt {
DeriveExampleOpt {
name: Some(self.name),
surname: self.surname,
surname2: self.surname2,
surname3: self.surname3,
id: Some (::optionable::OptionedConvert::from_optionable(self.id))
}
}
fn try_from_optioned(value:DeriveExampleOpt ) -> Result <Self, ::optionable::Error> {
Ok (Self {
name: value.name.ok_or(::optionable::Error { missing_field: "name" })?,
surname: value.surname,
surname2: value.surname2,
surname3: value.surname3,
id: ::optionable::OptionedConvert::try_into_optionable(value.id.ok_or(::optionable::Error{ missing_field: "id" })?)?
})
}
fn merge(&mut self, other: DeriveExampleOpt ) -> Result<(), ::optionable::Error> {
if let Some(other_value) = other.name {
self.name = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
self.surname = other.surname;
self.surname2 = other.surname2;
self.surname3 = other.surname3;
if let Some (other_value) = other.id {
::optionable::OptionedConvert::merge_into(other_value, &mut self.id)?;
}
Ok (())
}
}
#[automatically_derived]
impl ::optionable::merge::OptionableMapKeysEq for DeriveExample {
fn keys_eq(&self, other: &<Self as ::optionable::Optionable>::Optioned) -> bool {
self.surname == other.surname && self.surname3 == other.surname3
}
}
#[automatically_derived]
impl ::optionable::OptionedConvert<DeriveExample> for DeriveExampleOpt {
fn from_optionable(value: DeriveExample) -> Self {
::optionable::OptionableConvert::into_optioned(value)
}
fn try_into_optionable(self) -> Result<DeriveExample, ::optionable::Error> {
::optionable::OptionableConvert::try_from_optioned(self)
}
fn merge_into(self, other: &mut DeriveExample) -> Result<(), ::optionable::Error> {
::optionable::OptionableConvert::merge(other, self)
}
}
},
);
}
#[test]
fn test_named_struct_serde_attrs() {
assert_optionable(
quote! {
#[derive(Optionable)]
#[optionable(derive(Deserialize,Serialize,Default),suffix="Ac")]
#[optionable(attr_copy(attr=serde,key=rename))]
#[optionable_attr(serde(rename_all_fields = "camelCase", deny_unknown_fields))]
#[optionable_attr(serde(default))]
struct DeriveExample {
#[optionable_attr(serde(rename = "firstName"))]
name: String,
#[serde(rename="middle__name")]
middle_name: Option<String>,
surname: String,
}
},
quote! {
#[derive(Default, Deserialize, Serialize)]
#[serde(rename_all_fields = "camelCase", deny_unknown_fields)]
#[serde(default)]
struct DeriveExampleAc {
#[serde(rename = "firstName")]
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(rename = "middle__name")]
#[serde(skip_serializing_if = "Option::is_none")]
middle_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
surname: Option<String>
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExample {
type Optioned = DeriveExampleAc;
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExampleAc {
type Optioned = DeriveExampleAc;
}
#[automatically_derived]
impl ::optionable::OptionableConvert for DeriveExample {
fn into_optioned (self) -> DeriveExampleAc {
DeriveExampleAc {
name: Some(self.name),
middle_name: self.middle_name,
surname: Some(self.surname)
}
}
fn try_from_optioned(value: DeriveExampleAc ) -> Result <Self, ::optionable::Error> {
Ok(Self{
name: value.name.ok_or(::optionable::Error { missing_field: "name"})?,
middle_name: value.middle_name,
surname: value.surname.ok_or(::optionable::Error { missing_field: "surname"})?
})
}
fn merge(&mut self, other: DeriveExampleAc ) -> Result<(), ::optionable::Error> {
if let Some(other_value) = other.name {
self.name = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
if self.middle_name.is_none() {
self.middle_name = ::optionable::OptionableConvert::try_from_optioned(other.middle_name)?;
} else if let Some(self_value) = self.middle_name.as_mut() && let Some(other_value) = other.middle_name {
::optionable::OptionableConvert::merge(self_value, other_value)?;
}
if let Some(other_value) = other.surname {
self.surname = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
Ok(())
}
}
#[automatically_derived]
impl ::optionable::OptionedConvert<DeriveExample> for DeriveExampleAc {
fn from_optionable(value: DeriveExample) -> Self {
::optionable::OptionableConvert::into_optioned(value)
}
fn try_into_optionable(self) -> Result<DeriveExample, ::optionable::Error> {
::optionable::OptionableConvert::try_from_optioned(self)
}
fn merge_into(self, other: &mut DeriveExample) -> Result<(), ::optionable::Error> {
::optionable::OptionableConvert::merge(other, self)
}
}
},
);
}
#[test]
fn test_named_struct_option_wrap() {
assert_optionable(
quote! {
#[derive(Optionable)]
#[optionable(derive(Deserialize,Serialize,Default),suffix="Ac")]
#[optionable(attr_copy(attr=serde,key=rename))]
#[optionable(option_wrap)]
#[optionable_attr(serde(rename_all_fields = "camelCase", deny_unknown_fields))]
#[optionable_attr(serde(default))]
struct DeriveExample {
#[optionable_attr(serde(rename = "firstName"))]
name: String,
#[serde(rename="middle__name")]
middle_name: Option<String>,
surname: String,
}
},
quote! {
#[derive(Default, Deserialize, Serialize)]
#[serde(rename_all_fields = "camelCase", deny_unknown_fields)]
#[serde(default)]
struct DeriveExampleAc {
#[serde(rename = "firstName")]
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(rename = "middle__name")]
#[serde(skip_serializing_if = "Option::is_none")]
middle_name: Option<Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
surname: Option<String>
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExample {
type Optioned = DeriveExampleAc;
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExampleAc {
type Optioned = DeriveExampleAc;
}
#[automatically_derived]
impl ::optionable::OptionableConvert for DeriveExample {
fn into_optioned (self) -> DeriveExampleAc {
DeriveExampleAc {
name: Some(self.name),
middle_name: Some(self.middle_name),
surname: Some(self.surname)
}
}
fn try_from_optioned(value: DeriveExampleAc ) -> Result <Self, ::optionable::Error> {
Ok(Self{
name: value.name.ok_or(::optionable::Error { missing_field: "name"})?,
middle_name: value.middle_name.ok_or(::optionable::Error{ missing_field : "middle_name" })?,
surname: value.surname.ok_or(::optionable::Error { missing_field: "surname"})?
})
}
fn merge(&mut self, other: DeriveExampleAc ) -> Result<(), ::optionable::Error> {
if let Some(other_value) = other.name {
self.name = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
if let Some(other_value) = other.middle_name {
self.middle_name = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
if let Some(other_value) = other.surname {
self.surname = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
Ok(())
}
}
#[automatically_derived]
impl ::optionable::OptionedConvert<DeriveExample> for DeriveExampleAc {
fn from_optionable(value: DeriveExample) -> Self {
::optionable::OptionableConvert::into_optioned(value)
}
fn try_into_optionable(self) -> Result<DeriveExample, ::optionable::Error> {
::optionable::OptionableConvert::try_from_optioned(self)
}
fn merge_into(self, other: &mut DeriveExample) -> Result<(), ::optionable::Error> {
::optionable::OptionableConvert::merge(other, self)
}
}
},
);
}
#[test]
fn test_named_struct_serde_full_path() {
assert_optionable(
quote! {
#[derive(Optionable)]
#[optionable(derive(serde::Deserialize,serde::Serialize),suffix="Ac")]
struct DeriveExample {
name: String,
surname: String,
}
},
quote! {
#[derive(serde::Deserialize, serde::Serialize)]
struct DeriveExampleAc {
#[serde(skip_serializing_if = "Option::is_none")]
name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
surname: Option<String>
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExample {
type Optioned = DeriveExampleAc;
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExampleAc {
type Optioned = DeriveExampleAc;
}
#[automatically_derived]
impl ::optionable::OptionableConvert for DeriveExample {
fn into_optioned (self) -> DeriveExampleAc {
DeriveExampleAc {
name: Some(self.name),
surname: Some(self.surname)
}
}
fn try_from_optioned(value: DeriveExampleAc ) -> Result <Self, ::optionable::Error> {
Ok(Self{
name: value.name.ok_or(::optionable::Error{ missing_field: "name"})?,
surname: value.surname.ok_or(::optionable::Error{ missing_field: "surname"})?
})
}
fn merge(&mut self, other: DeriveExampleAc ) -> Result<(), ::optionable::Error> {
if let Some(other_value) = other.name {
self.name = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
if let Some(other_value) = other.surname {
self.surname = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
Ok(())
}
}
#[automatically_derived]
impl ::optionable::OptionedConvert<DeriveExample> for DeriveExampleAc {
fn from_optionable(value: DeriveExample) -> Self {
::optionable::OptionableConvert::into_optioned(value)
}
fn try_into_optionable(self) -> Result<DeriveExample, ::optionable::Error> {
::optionable::OptionableConvert::try_from_optioned(self)
}
fn merge_into(self, other: &mut DeriveExample) -> Result<(), ::optionable::Error> {
::optionable::OptionableConvert::merge(other, self)
}
}
},
);
}
#[test]
fn test_unnamed_struct_fields() {
assert_optionable(
quote! {
#[derive(Optionable)]
struct DeriveExample(pub String, i32);
},
quote! {
struct DeriveExampleOpt(
pub Option<String>,
Option<i32>
);
#[automatically_derived]
impl ::optionable::Optionable for DeriveExample {
type Optioned = DeriveExampleOpt;
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExampleOpt {
type Optioned = DeriveExampleOpt;
}
#[automatically_derived]
impl ::optionable::OptionableConvert for DeriveExample {
fn into_optioned (self) -> DeriveExampleOpt {
DeriveExampleOpt (
Some(self.0),
Some(self.1)
)
}
fn try_from_optioned(value: DeriveExampleOpt ) -> Result <Self, ::optionable::Error> {
Ok(Self(
value.0.ok_or(::optionable::Error { missing_field:"0" })?,
value.1.ok_or(::optionable::Error { missing_field: "1" })?
))
}
fn merge(&mut self, other: DeriveExampleOpt ) -> Result<(), ::optionable::Error> {
if let Some(other_value) = other.0 {
self.0 = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
if let Some (other_value) = other.1 {
self.1 = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
Ok (())
}
}
#[automatically_derived]
impl ::optionable::OptionedConvert<DeriveExample> for DeriveExampleOpt {
fn from_optionable(value: DeriveExample) -> Self {
::optionable::OptionableConvert::into_optioned(value)
}
fn try_into_optionable(self) -> Result<DeriveExample, ::optionable::Error> {
::optionable::OptionableConvert::try_from_optioned(self)
}
fn merge_into(self, other: &mut DeriveExample) -> Result<(), ::optionable::Error> {
::optionable::OptionableConvert::merge(other, self)
}
}
},
);
}
#[test]
fn test_unnamed_struct_required() {
assert_optionable(
quote! {
#[derive(Optionable)]
struct DeriveExample(pub String, #[optionable(required)] i32);
},
quote! {
struct DeriveExampleOpt(
pub Option<String>,
i32
);
#[automatically_derived]
impl ::optionable::Optionable for DeriveExample {
type Optioned = DeriveExampleOpt;
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExampleOpt {
type Optioned = DeriveExampleOpt;
}
# [automatically_derived]
impl ::optionable::OptionableConvert for DeriveExample {
fn into_optioned (self) -> DeriveExampleOpt {
DeriveExampleOpt (
Some(self.0),
self.1
)
}
fn try_from_optioned(value: DeriveExampleOpt ) -> Result <Self, ::optionable::Error> {
Ok(Self(
value.0.ok_or(::optionable::Error { missing_field: "0" })?,
value.1))
}
fn merge(&mut self, other: DeriveExampleOpt ) -> Result<(), ::optionable::Error> {
if let Some(other_value) = other.0 {
self.0 = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
self.1 = other.1;
Ok (())
}
}
#[automatically_derived]
impl ::optionable::OptionedConvert<DeriveExample> for DeriveExampleOpt {
fn from_optionable(value: DeriveExample) -> Self {
::optionable::OptionableConvert::into_optioned(value)
}
fn try_into_optionable(self) -> Result<DeriveExample, ::optionable::Error> {
::optionable::OptionableConvert::try_from_optioned(self)
}
fn merge_into(self, other: &mut DeriveExample) -> Result<(), ::optionable::Error> {
::optionable::OptionableConvert::merge(other, self)
}
}
},
);
}
#[test]
fn test_named_struct_generics() {
assert_optionable(
quote! {
#[derive(Optionable)]
#[optionable(derive(Serialize, Deserialize))]
struct DeriveExample<'a, T, T2: Serialize, T3> {
output: T,
input: Cow<'a, T2>,
#[optionable(required)]
extra: T3,
}
},
quote! {
#[derive (Deserialize, Serialize)]
struct DeriveExampleOpt<'a, T, T2: Serialize, T3>
where T: ::optionable::Optionable,
<T as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize,
T2: ::optionable::Optionable,
<T2 as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize
{
#[serde(skip_serializing_if="Option::is_none")]
output: Option< <T as ::optionable::Optionable>::Optioned>,
#[serde(skip_serializing_if="Option::is_none")]
input: Option< Cow<'a, <T2 as ::optionable::Optionable>::Optioned>>,
extra: T3
}
#[automatically_derived]
impl<'a, T, T2: Serialize, T3> ::optionable::Optionable for DeriveExample<'a, T, T2, T3>
where T: ::optionable::Optionable,
<T as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize,
T2: ::optionable::Optionable,
<T2 as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize
{
type Optioned = DeriveExampleOpt<'a, T,T2,T3>;
}
#[automatically_derived]
impl<'a, T, T2: Serialize, T3> ::optionable::Optionable for DeriveExampleOpt<'a ,T, T2, T3>
where T: ::optionable::Optionable,
<T as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize,
T2: ::optionable::Optionable,
<T2 as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize
{
type Optioned = DeriveExampleOpt<'a, T, T2, T3>;
}
#[automatically_derived]
impl <'a, T, T2:Serialize, T3> ::optionable::OptionableConvert for DeriveExample<'a, T, T2, T3>
where T: ::optionable::OptionableConvert,
<T as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize,
T2: ::optionable::OptionableConvert,
<T2 as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize
{
fn into_optioned (self) -> DeriveExampleOpt<'a, T, T2, T3> {
DeriveExampleOpt::<'a, T, T2, T3> {
output: Some(::optionable::OptionableConvert::into_optioned(self.output)),
input: Some(::optionable::OptionableConvert::into_optioned(self.input)),
extra: self.extra
}
}
fn try_from_optioned(value: DeriveExampleOpt<'a, T, T2, T3> ) -> Result <Self, ::optionable::Error> {
Ok(Self{
output: ::optionable::OptionableConvert::try_from_optioned(value.output.ok_or(::optionable::Error { missing_field: "output" })?)?,
input: ::optionable::OptionableConvert::try_from_optioned(value.input.ok_or(::optionable::Error { missing_field: "input" })?)?,
extra: value.extra
})
}
fn merge(&mut self, other: DeriveExampleOpt<'a, T, T2, T3> ) -> Result<(), ::optionable::Error> {
if let Some(other_value) = other.output {
::optionable::OptionableConvert::merge(&mut self.output, other_value)?;
}
if let Some(other_value) = other.input {
::optionable::OptionableConvert::merge(&mut self.input, other_value)?;
}
self.extra=other.extra;
Ok(())
}
}
#[automatically_derived]
impl <'a, T, T2:Serialize, T3> ::optionable::OptionedConvert<DeriveExample<'a, T, T2, T3> > for DeriveExampleOpt<'a, T, T2, T3>
where T: ::optionable::OptionableConvert,
<T as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize,
T2: ::optionable::OptionableConvert,
<T2 as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize
{
fn from_optionable(value: DeriveExample<'a, T, T2, T3>) -> Self {
::optionable::OptionableConvert::into_optioned(value)
}
fn try_into_optionable(self) -> Result<DeriveExample<'a, T, T2, T3>, ::optionable::Error> {
::optionable::OptionableConvert::try_from_optioned(self)
}
fn merge_into(self, other: &mut DeriveExample<'a, T, T2, T3>) -> Result<(), ::optionable::Error> {
::optionable::OptionableConvert::merge(other, self)
}
}
},
);
}
#[test]
fn test_enum_mixed_variants() {
assert_optionable(
quote! {
#[derive(Optionable)]
enum DeriveExample {
Unit,
Plain(String),
Address{street: String, number: u32},
Address2(String,u32),
}
},
quote! {
enum DeriveExampleOpt {
Unit,
Plain( Option<String> ),
Address{ street: Option<String>, number:Option<u32> },
Address2( Option<String>, Option<u32> )
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExample {
type Optioned = DeriveExampleOpt;
}
#[automatically_derived]
impl ::optionable::Optionable for DeriveExampleOpt {
type Optioned = DeriveExampleOpt;
}
#[automatically_derived]
impl ::optionable::OptionableConvert for DeriveExample {
fn into_optioned (self) -> DeriveExampleOpt {
match self{
Self::Unit => DeriveExampleOpt::Unit,
Self::Plain(self_0) => DeriveExampleOpt::Plain(
Some(self_0)
),
Self::Address{street: self_street, number: self_number} => DeriveExampleOpt::Address{
street: Some(self_street),
number: Some(self_number)
},
Self::Address2(self_0, self_1) => DeriveExampleOpt::Address2(
Some(self_0),
Some(self_1)
)
}
}
fn try_from_optioned(other: DeriveExampleOpt) -> Result <Self, ::optionable::Error> {
Ok (match other {
DeriveExampleOpt::Unit => Self::Unit,
DeriveExampleOpt::Plain(other_0) => Self::Plain(
other_0.ok_or(::optionable::Error { missing_field: "0" })?
),
DeriveExampleOpt::Address{street: other_street, number: other_number} => Self::Address{
street: other_street.ok_or(::optionable::Error { missing_field: "street" })?,
number: other_number.ok_or(::optionable::Error { missing_field: "number" })?
},
DeriveExampleOpt::Address2(other_0, other_1) => Self::Address2(
other_0.ok_or(::optionable::Error { missing_field: "0"})?,
other_1.ok_or(::optionable::Error { missing_field: "1"})?)
})
}
fn merge(&mut self, other: DeriveExampleOpt) -> Result<(), ::optionable::Error> {
match other {
DeriveExampleOpt::Unit => {
if let Self::Unit = self {} else {
*self = Self::try_from_optioned(DeriveExampleOpt::Unit)?;
}
},
DeriveExampleOpt::Plain(other_0) => {
if let Self::Plain(self_0) = self{
if let Some(other_value) = other_0 {
*self_0 = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
} else {
*self = Self::try_from_optioned(DeriveExampleOpt::Plain(other_0))?;
}
},
DeriveExampleOpt::Address{street: other_street, number: other_number} => {
if let Self::Address{street: self_street, number: self_number} = self{
if let Some(other_value) = other_street {
*self_street = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
if let Some(other_value) = other_number {
*self_number = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
} else {
*self = Self::try_from_optioned(DeriveExampleOpt::Address{street: other_street, number: other_number})?;
}
},
DeriveExampleOpt::Address2(other_0, other_1) => {
if let Self::Address2(self_0, self_1) = self{
if let Some(other_value) = other_0 {
*self_0 = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
if let Some(other_value) = other_1 {
*self_1 = ::optionable::OptionableConvert::try_from_optioned(other_value)?;
}
} else {
*self = Self::try_from_optioned(DeriveExampleOpt::Address2(other_0, other_1))?;
}
}
}
Ok(())
}
}
#[automatically_derived]
impl ::optionable::OptionedConvert<DeriveExample> for DeriveExampleOpt {
fn from_optionable(value: DeriveExample) -> Self {
::optionable::OptionableConvert::into_optioned(value)
}
fn try_into_optionable(self) -> Result<DeriveExample, ::optionable::Error> {
::optionable::OptionableConvert::try_from_optioned(self)
}
fn merge_into(self, other: &mut DeriveExample) -> Result<(), ::optionable::Error> {
::optionable::OptionableConvert::merge(other, self)
}
}
},
);
}
#[test]
fn test_enum_unit_variants() {
assert_optionable(
quote! {
#[derive(Optionable)]
enum DeriveExample {
Unit,
Unit2,
Unit3
}
},
quote! {
#[automatically_derived]
impl ::optionable::Optionable for DeriveExample {
type Optioned = Self;
}
#[automatically_derived]
impl ::optionable::OptionableConvert for DeriveExample {
fn into_optioned(self) -> DeriveExample{
self
}
fn try_from_optioned(value: Self::Optioned) -> Result <Self , ::optionable::Error> {
Ok (value)
}
fn merge (&mut self , other: Self::Optioned) -> Result <() , ::optionable::Error> {
*self = other;
Ok(())
}
}
},
);
}
#[test]
fn test_crate_replacement() {
let input = quote! {
#[derive(Optionable)]
struct DeriveExample {
name: crate::Name,
pub surname: Box<crate::SurName>,
}
};
let expected = quote! {
struct DeriveExampleOpt {
name: Option< <::testcrate::Name as crate::Optionable>::Optioned>,
pub surname: Option< Box< <::testcrate::SurName as crate::Optionable>::Optioned>>
}
#[automatically_derived]
impl crate::Optionable for crate_prefix::DeriveExample {
type Optioned = DeriveExampleOpt;
}
#[automatically_derived]
impl crate::Optionable for DeriveExampleOpt {
type Optioned = DeriveExampleOpt;
}
#[automatically_derived]
impl crate::OptionableConvert for crate_prefix::DeriveExample {
fn into_optioned (self) -> DeriveExampleOpt {
DeriveExampleOpt {
name: Some(crate::OptionableConvert::into_optioned(self.name)),
surname: Some(crate::OptionableConvert::into_optioned(self.surname))
}
}
fn try_from_optioned(value: DeriveExampleOpt ) -> Result <Self, crate::Error> {
Ok(Self{
name: crate::OptionableConvert::try_from_optioned(value.name.ok_or(crate::Error { missing_field: "name" })?)?,
surname: crate::OptionableConvert::try_from_optioned(value.surname.ok_or(crate::Error { missing_field: "surname" })?)?
})
}
fn merge(&mut self, other: DeriveExampleOpt ) -> Result<(), crate::Error> {
if let Some(other_value) = other.name {
crate::OptionableConvert::merge(&mut self.name, other_value)?;
}
if let Some(other_value) = other.surname {
crate::OptionableConvert::merge(&mut self.surname, other_value)?;
}
Ok(())
}
}
#[automatically_derived]
impl crate::OptionedConvert<crate_prefix::DeriveExample> for DeriveExampleOpt {
fn from_optionable(value: crate_prefix::DeriveExample) -> Self {
crate::OptionableConvert::into_optioned(value)
}
fn try_into_optionable(self) -> Result<crate_prefix::DeriveExample, crate::Error> {
crate::OptionableConvert::try_from_optioned(self)
}
fn merge_into(self, other: &mut crate_prefix::DeriveExample) -> Result<(), crate::Error> {
crate::OptionableConvert::merge(other, self)
}
}
};
let parsed = syn::parse2(input).unwrap();
let output = derive_optionable(
parsed,
Some(&CodegenSettings {
ty_prefix: Some(Path::from_string("crate_prefix").unwrap()),
optionable_crate_name: Path::from_string("crate").unwrap(),
input_crate_replacement: Some(parse_quote!(testcrate)),
}),
)
.unwrap();
assert_eq!(
normalize_token_str(&expected.to_string()),
normalize_token_str(&output.to_string())
);
}
}