mod helper;
mod k8s_openapi;
mod parsed_input;
mod where_clause;
use crate::helper::{destructure, error, error_on_helper_attributes, is_serialize, struct_wrapper};
use crate::k8s_openapi::{
error_missing_features, k8s_adjust_fields, k8s_derives, k8s_openapi_impl_metadata,
k8s_openapi_impl_resource, k8s_resource_type, k8s_type_attr,
};
use crate::parsed_input::{
into_field_handling, FieldHandling, FieldParsed, StructParsed, StructType,
};
use crate::where_clause::{where_clauses, WhereClauses};
use darling::util::PathList;
use darling::{FromAttributes, FromDeriveInput, FromMeta};
use itertools::MultiUnzip;
use proc_macro2::{Ident, Literal, TokenStream};
use quote::{format_ident, quote, ToTokens};
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
use std::default::Default;
use syn::parse::Parser;
use syn::spanned::Spanned;
use syn::{
parse_quote, Attribute, Data, DeriveInput, Error, Field, LitStr, Meta, MetaList, Path, Type,
TypePath, WhereClause,
};
const HELPER_IDENT: &str = "optionable";
const HELPER_ATTR_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)]
derive: Vec<PathList>,
suffix: Option<LitStr>,
no_convert: Option<()>,
k8s_openapi: Option<TypeHelperAttributesK8sOpenapi>,
kube: Option<TypeHelperAttributesKube>,
#[darling(multiple)]
attr_copy: Vec<FieldAttributeToCopy>,
}
#[derive(FromMeta)]
pub(crate) struct TypeHelperAttributesK8sOpenapi {
metadata: Option<()>,
resource: Option<()>,
}
#[derive(FromMeta)]
pub(crate) struct TypeHelperAttributesKube {
resource: Option<()>,
}
#[derive(FromMeta)]
pub struct FieldAttributeToCopy {
pub attr: Path,
pub key: Option<Path>,
}
#[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();
if let Some(entry) = result.get_mut(&path_serde) {
if !entry.is_empty() {
entry.insert(path_rename);
}
} else {
result.insert(path_serde, HashSet::from([path_rename]));
}
}
result
}
impl Default for CodegenSettings {
fn default() -> Self {
Self {
optionable_crate_name: parse_quote!(::optionable),
ty_prefix: None,
input_crate_replacement: None,
}
}
}
#[derive(FromAttributes)]
#[darling(attributes(optionable))]
struct FieldHelperAttributes {
required: Option<()>,
}
#[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),*))])
}
#[allow(clippy::too_many_lines)]
#[allow(clippy::items_after_statements)]
pub fn derive_optionable(
input: DeriveInput,
settings: Option<&CodegenSettings>,
) -> syn::Result<TokenStream> {
let attrs = TypeHelperAttributes::from_derive_input(&input)?;
error_missing_features(&attrs)?;
let k8s_resource_type = k8s_resource_type(&attrs)?;
let attr_copy_identifier = field_attr_copy_hashmap(attrs.attr_copy, attrs.kube.as_ref());
let mut derive = attrs
.derive
.into_iter()
.flat_map(|el| el.iter().cloned().collect::<Vec<_>>())
.collect::<Vec<_>>();
if (attrs.k8s_openapi.is_some() || attrs.kube.is_some())
&& let Some(k8s_derives) = &mut k8s_derives(&input)
{
derive.append(k8s_derives);
}
let settings = settings.map(Cow::Borrowed).unwrap_or_default();
let crate_name = &settings.optionable_crate_name;
let ty_attr_forwarded = forwarded_attributes(&input.attrs, &attr_copy_identifier)?;
let k8s_openapi_attrs =
(attrs.k8s_openapi.is_some() || attrs.kube.is_some()).then(|| k8s_type_attr(&input));
let vis = input.vis;
let ty_ident_opt = {
let suffix = attrs.suffix.map_or_else(
|| {
if attrs.kube.is_some() || attrs.k8s_openapi.is_some() {
Cow::Borrowed("Ac")
} else {
Cow::Borrowed("Opt")
}
},
|s| Cow::Owned(s.value()),
);
Ident::new(&(input.ident.to_string() + &suffix), input.ident.span())
};
let ty_ident = if let Some(mut ty_prefix) = settings.ty_prefix.clone() {
ty_prefix.segments.push(input.ident.into());
ty_prefix
} else {
input.ident.into()
};
let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
let generics_colon = (!input.generics.params.is_empty()).then(|| quote! {::});
let skip_optionable_if_serde_serialize = (attrs.k8s_openapi.is_some() || derive.iter().any(is_serialize))
.then(|| quote!(#[serde(skip_serializing_if = "Option::is_none")]));
struct Derived {
enum_struct: TokenStream,
fields: TokenStream,
where_clause_struct_enum: WhereClause,
where_clause_impl_optionable: WhereClause,
impl_optionable_convert: Option<TokenStream>,
}
let Derived {
enum_struct,
fields,
where_clause_struct_enum,
where_clause_impl_optionable,
impl_optionable_convert,
} = match input.data {
Data::Struct(s) => {
let mut struct_parsed = into_field_handling(
crate_name.to_owned(),
s.fields,
settings.input_crate_replacement.as_ref(),
)?;
k8s_adjust_fields(
&mut struct_parsed,
attrs.k8s_openapi.as_ref(),
attrs.kube.as_ref(),
k8s_resource_type.as_ref(),
crate_name,
&ty_ident_opt,
&ty_generics,
)?;
let WhereClauses {
struct_enum_def: where_clause_struct_enum,
impl_optionable: where_clause_impl_optionable,
impl_optionable_convert: where_clause_impl_optionable_convert,
} = where_clauses(
crate_name,
settings.input_crate_replacement.as_ref(),
&input.generics,
&derive,
attrs.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,
skip_optionable_if_serde_serialize.as_ref(),
&attr_copy_identifier,
)?;
let impl_optionable_convert = attrs.no_convert.is_none().then(|| {
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);
quote! {
#[automatically_derived]
impl #impl_generics #crate_name::OptionableConvert for #ty_ident #ty_generics #where_clause_impl_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(())
}
}
}
});
Derived {
enum_struct: quote! {struct},
fields: quote! {#optioned_fields #unnamed_struct_semicolon},
where_clause_struct_enum,
where_clause_impl_optionable,
impl_optionable_convert,
}
}
Data::Enum(e) => {
let self_prefix = quote! {self_};
let other_prefix = quote! {other_};
let variants = e
.variants
.into_iter()
.map(|v| {
error_on_helper_attributes(&v.attrs, ERR_MSG_HELPER_ATTR_ENUM_VARIANTS)?;
let mut field_handling = into_field_handling(
crate_name.to_owned(),
v.fields,
settings.input_crate_replacement.as_ref(),
)?;
k8s_adjust_fields(
&mut field_handling,
attrs.k8s_openapi.as_ref(),
attrs.kube.as_ref(),
k8s_resource_type.as_ref(),
crate_name,
&ty_ident_opt,
&ty_generics,
)?;
Ok::<_, Error>((
v.ident,
forwarded_attributes(&v.attrs, &attr_copy_identifier)?,
field_handling,
))
})
.collect::<Result<Vec<_>, _>>()?;
let all_fields = variants
.iter()
.flat_map(|(_, _, fields)| &fields.fields)
.collect::<Vec<_>>();
let WhereClauses {
struct_enum_def: where_clause_struct_enum,
impl_optionable: where_clause_impl_optionable,
impl_optionable_convert: where_clause_impl_optionable_convert,
} = where_clauses(
crate_name,
settings.input_crate_replacement.as_ref(),
&input.generics,
&derive,
attrs.no_convert.is_some(),
all_fields,
);
let optioned_variants = variants
.iter()
.map(|(variant, forward_attrs, f)| {
let fields = optioned_fields(
f,
skip_optionable_if_serde_serialize.as_ref(),
&attr_copy_identifier,
)?;
Ok::<_, Error>(quote!( #forward_attrs #variant #fields ))
})
.collect::<Result<Vec<_>, _>>()?;
let impl_optionable_convert = attrs.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_impl_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()?;
Derived {
enum_struct: quote! {enum},
fields: quote! {{#(#optioned_variants),*}},
where_clause_struct_enum,
where_clause_impl_optionable,
impl_optionable_convert,
}
}
Data::Union(_) => return error("#[derive(Optionable)] not supported for unit structs"),
};
let derives = (!derive.is_empty()).then(|| quote! {#[derive(#(#derive),*)]});
let k8s_openapi_impl_resource = attrs
.k8s_openapi
.as_ref()
.is_some_and(|attr| attr.resource.is_some())
.then(|| {
k8s_openapi_impl_resource(
&ty_ident,
&ty_ident_opt,
&impl_generics,
&ty_generics,
&where_clause_impl_optionable,
)
});
let impl_k8s_metadata = attrs
.k8s_openapi
.as_ref()
.is_some_and(|attr| attr.metadata.is_some())
.then(|| {
k8s_openapi_impl_metadata(
&ty_ident,
&ty_ident_opt,
&impl_generics,
&ty_generics,
&where_clause_impl_optionable,
)
});
let kube_derive_resource = attrs
.kube
.is_some_and(|attr| attr.resource.is_some())
.then(|| {
quote! {
#[derive(kube::Resource)]
#[resource(inherit = #ty_ident )]
}
});
Ok(quote! {
#derives
#kube_derive_resource
#ty_attr_forwarded
#k8s_openapi_attrs
#vis #enum_struct #ty_ident_opt #impl_generics #where_clause_struct_enum #fields
#[automatically_derived]
impl #impl_generics #crate_name::Optionable for #ty_ident #ty_generics #where_clause_impl_optionable {
type Optioned = #ty_ident_opt #ty_generics;
}
#[automatically_derived]
impl #impl_generics #crate_name::Optionable for #ty_ident_opt #ty_generics #where_clause_impl_optionable {
type Optioned = #ty_ident_opt #ty_generics;
}
#impl_optionable_convert
#k8s_openapi_impl_resource
#impl_k8s_metadata
})
}
fn optioned_fields(
fields: &StructParsed,
serde_attributes: 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,
}| {
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 {
FieldHandling::Required | FieldHandling::OptionedOnly => quote! {#forwarded_attrs #vis #ident #colon #ty},
FieldHandling::IsOption => quote! {#forwarded_attrs #serde_attributes #vis #ident #colon #optioned_ty},
FieldHandling::Other => quote! {#forwarded_attrs #serde_attributes #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 })| {
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)) {
(FieldHandling::Required, _) | (FieldHandling::IsOption, true) => quote! {#ident #colon #self_selector},
(FieldHandling::IsOption, false) => quote! {#ident #colon #crate_name::OptionableConvert::into_optioned(#self_selector)},
(FieldHandling::Other, true) => quote! {#ident #colon Some(#self_selector)},
(FieldHandling::Other, 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().map(|(i, FieldParsed { field: Field { ident, ty, .. }, handling })| {
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)) {
(FieldHandling::Required, _) | (FieldHandling::IsOption, true) => quote! {#ident #colon value.#selector},
(FieldHandling::IsOption, false) => quote! {
#ident #colon #crate_name::OptionableConvert::try_from_optioned(
#value_selector
)?
},
(FieldHandling::Other, true) => {
let selector_quoted = LitStr::new(&selector.to_string(), ident.span());
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());
quote! {
#ident #colon #crate_name::OptionableConvert::try_from_optioned(#value_selector.ok_or(#crate_name::Error{ missing_field: #selector_quoted })?
)?
}
}
(FieldHandling::OptionedOnly, _) => TokenStream::default(),
}
});
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,
) -> TokenStream {
let fields_token = struct_parsed.fields.iter().enumerate().map(
|(
i,
FieldParsed {
field: Field { ident, ty, .. },
handling,
},
)| {
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;
match (handling, is_self_resolving_optioned(ty)) {
(FieldHandling::Required, _) | (FieldHandling::IsOption, true) => quote! {#deref_modifier #self_selector = #other_selector;},
(FieldHandling::IsOption, false) => quote! {
#crate_name::OptionableConvert::merge(#self_merge_mut_modifier #self_selector, #other_selector)?;
},
(FieldHandling::Other, true) => quote! {
if let Some(other_value)=#other_selector{
#deref_modifier #self_selector = other_value;
}
},
(FieldHandling::Other, false) => quote! {
if let Some(other_value)=#other_selector{
#crate_name::OptionableConvert::merge(#self_merge_mut_modifier #self_selector, other_value)?;
}
},
(FieldHandling::OptionedOnly, _) => TokenStream::default(),
}
},
);
quote! {
#(#fields_token)*
}
}
fn optioned_ty(crate_name: &Path, ty: &Type) -> TokenStream {
if is_self_resolving_optioned(ty) {
ty.to_token_stream()
} else {
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",
];
fn is_self_resolving_optioned(ty: &Type) -> bool {
if let Type::Path(TypePath { qself, path }) = &ty
&& qself.is_none()
&& SELF_RESOLVING_TYPES.contains(&&*path.to_token_stream().to_string())
{
true
} else {
false
}
}
fn forwarded_attributes(
attrs: &[Attribute],
attr_to_copy: &HashMap<Path, HashSet<Path>>,
) -> Result<Option<TokenStream>, Error> {
let forward_attrs = attrs
.iter()
.map(|attr| {
if attr.path().is_ident(HELPER_ATTR_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(){
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>= syn::punctuated::Punctuated::<Meta, syn::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 mut attr=attr.clone();
let mut meta_list=meta_list.clone();
meta_list.tokens=inner_metas.into_iter().collect();
attr.meta=meta_list.into();
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::{derive_optionable, CodegenSettings};
use darling::FromMeta;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_quote, Path};
struct TestCase {
input: TokenStream,
output: TokenStream,
}
#[test]
#[allow(clippy::too_many_lines)]
fn test_optionable() {
let tcs = vec![
TestCase {
input: quote! {
#[derive(Optionable)]
struct DeriveExample {
name: String,
pub surname: String,
}
},
output: 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 = other_value;
}
if let Some(other_value) = other.surname {
self.surname = other_value;
}
Ok(())
}
}
},
},
TestCase {
input: quote! {
#[derive(Optionable)]
#[optionable(no_convert)]
struct DeriveExample {
name: String,
pub surname: String,
}
},
output: 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;
}
},
},
TestCase {
input: quote! {
#[derive(Optionable)]
struct DeriveExample {
name: String,
#[optionable(required)]
pub surname: String,
}
},
output: quote! {
struct DeriveExampleOpt {
name: Option<String>,
pub surname: 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: 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
})
}
fn merge(&mut self, other: DeriveExampleOpt ) -> Result<(), ::optionable::Error> {
if let Some(other_value) = other.name {
self.name = other_value;
}
self.surname = other.surname;
Ok (())
}
}
},
},
TestCase {
input: quote! {
#[derive(Optionable)]
#[optionable(derive(Deserialize,Serialize,Default),suffix="Ac")]
#[optionable(attr_copy(attr=serde,key=rename))]
#[optionable_attr(serde(rename_all = "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,
}
},
output: quote! {
#[derive(Deserialize, Serialize,Default)]
#[serde(rename_all = "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> as ::optionable::Optionable>::Optioned,
#[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: ::optionable::OptionableConvert::into_optioned(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: ::optionable::OptionableConvert::try_from_optioned(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 = other_value;
}
::optionable::OptionableConvert::merge(&mut self.middle_name, other.middle_name)?;
if let Some(other_value) = other.surname {
self.surname = other_value;
}
Ok(())
}
}
},
},
TestCase {
input: quote! {
#[derive(Optionable)]
#[optionable(derive(serde::Deserialize,serde::Serialize),suffix="Ac")]
struct DeriveExample {
name: String,
surname: String,
}
},
output: 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 = other_value;
}
if let Some(other_value) = other.surname {
self.surname = other_value;
}
Ok(())
}
}
},
},
TestCase {
input: quote! {
#[derive(Optionable)]
struct DeriveExample(pub String, i32);
},
output: 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 = other_value;
}
if let Some (other_value) = other.1 {
self.1 = other_value;
}
Ok (())
}
}
},
},
TestCase {
input: quote! {
#[derive(Optionable)]
struct DeriveExample(pub String, #[optionable(required)] i32);
},
output: 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 = other_value;
}
self.1 = other.1;
Ok (())
}
}
},
},
TestCase {
input: quote! {
#[derive(Optionable)]
#[optionable(derive(Serialize, Deserialize))]
struct DeriveExample<T, T2: Serialize, T3> {
output: T,
input: T2,
#[optionable(required)]
extra: T3,
}
},
output: quote! {
#[derive (Serialize , Deserialize)]
struct DeriveExampleOpt<T, T2: Serialize, T3>
where T: ::optionable::Optionable,
<T as ::optionable::Optionable>::Optioned: Sized + Serialize + serde::de::DeserializeOwned,
T2: ::optionable::Optionable,
<T2 as ::optionable::Optionable>::Optioned: Sized + Serialize + serde::de::DeserializeOwned
{
#[serde(skip_serializing_if="Option::is_none")]
output: Option< <T as ::optionable::Optionable>::Optioned>,
#[serde(skip_serializing_if="Option::is_none")]
input: Option< <T2 as ::optionable::Optionable>::Optioned>,
extra: T3
}
#[automatically_derived]
impl<T, T2: Serialize, T3> ::optionable::Optionable for DeriveExample<T, T2, T3>
where T: ::optionable::Optionable,
<T as ::optionable::Optionable>::Optioned: Sized + Serialize + serde::de::DeserializeOwned,
T2: ::optionable::Optionable,
<T2 as ::optionable::Optionable>::Optioned: Sized + Serialize + serde::de::DeserializeOwned
{
type Optioned = DeriveExampleOpt<T,T2,T3>;
}
#[automatically_derived]
impl<T, T2: Serialize, T3> ::optionable::Optionable for DeriveExampleOpt<T, T2, T3>
where T: ::optionable::Optionable,
<T as ::optionable::Optionable>::Optioned: Sized + Serialize + serde::de::DeserializeOwned,
T2: ::optionable::Optionable,
<T2 as ::optionable::Optionable>::Optioned: Sized + Serialize + serde::de::DeserializeOwned
{
type Optioned = DeriveExampleOpt<T, T2, T3>;
}
#[automatically_derived]
impl <T, T2:Serialize, T3> ::optionable::OptionableConvert for DeriveExample<T, T2, T3>
where T: ::optionable::OptionableConvert,
<T as ::optionable::Optionable>::Optioned: Sized + Serialize + serde::de::DeserializeOwned,
T2: ::optionable::OptionableConvert,
<T2 as ::optionable::Optionable>::Optioned: Sized + Serialize + serde::de::DeserializeOwned
{
fn into_optioned (self) -> DeriveExampleOpt<T, T2, T3> {
DeriveExampleOpt::<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<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<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(())
}
}
},
},
TestCase {
input: quote! {
#[derive(Optionable)]
enum DeriveExample {
Unit,
Plain(String),
Address{street: String, number: u32},
Address2(String,u32),
}
},
output: 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 = 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 = other_value;
}
if let Some(other_value) = other_number {
*self_number = 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 = other_value;
}
if let Some(other_value) = other_1 {
*self_1 = other_value;
}
} else {
*self = Self::try_from_optioned(DeriveExampleOpt::Address2(other_0, other_1))?;
}
}
}
Ok(())
}
}
},
},
];
for tc in tcs {
let input = syn::parse2(tc.input).unwrap();
let output = derive_optionable(input, None).unwrap();
println!("{output}");
assert_eq!(tc.output.to_string(), output.to_string());
}
}
#[test]
#[allow(clippy::too_many_lines)]
fn test_crate_replacement() {
let tcs = vec![TestCase {
input: quote! {
#[derive(Optionable)]
struct DeriveExample {
name: crate::Name,
pub surname: Box<crate::SurName>,
}
},
output: 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(())
}
}
},
}];
for tc in tcs {
let input = syn::parse2(tc.input).unwrap();
let output = derive_optionable(
input,
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!(tc.output.to_string(), output.to_string());
}
}
}