1mod helper;
14mod k8s_openapi;
15mod parsed_input;
16mod where_clause;
17
18use crate::helper::{destructure, error, error_on_helper_attributes, is_serialize, struct_wrapper};
19use crate::k8s_openapi::{
20 error_missing_features, k8s_adjust_fields, k8s_derives, k8s_openapi_impl_metadata,
21 k8s_openapi_impl_resource, k8s_resource_type, k8s_type_attr,
22};
23use crate::parsed_input::{
24 into_field_handling, FieldHandling, FieldParsed, StructParsed, StructType,
25};
26use crate::where_clause::{where_clauses, WhereClauses};
27use darling::util::PathList;
28use darling::{FromAttributes, FromDeriveInput, FromMeta};
29use itertools::MultiUnzip;
30use proc_macro2::{Ident, Literal, TokenStream};
31use quote::{format_ident, quote, ToTokens};
32use std::borrow::Cow;
33use std::collections::{BTreeSet, HashMap, HashSet};
34use std::default::Default;
35use syn::parse::Parser;
36use syn::punctuated::Punctuated;
37use syn::spanned::Spanned;
38use syn::{
39 parse_quote, Attribute, Data, DeriveInput, Error, Field, LitStr, Meta, MetaList, Path, Token,
40 Type, TypePath, WhereClause,
41};
42
43const HELPER_IDENT: &str = "optionable";
44const HELPER_ATTR_IDENT: &str = "optionable_attr";
45const ERR_MSG_HELPER_ATTR_ENUM_VARIANTS: &str =
46 "#[optionable] helper attributes not supported on enum variant level.";
47
48#[derive(FromDeriveInput)]
49#[darling(attributes(optionable))]
50pub(crate) struct TypeHelperAttributes {
52 #[darling(multiple)]
56 attr_copy: Vec<FieldAttributeToCopy>,
57 #[darling(multiple)]
59 derive: Vec<PathList>,
60 suffix: Option<LitStr>,
62 no_convert: Option<()>,
64 k8s_openapi: Option<TypeHelperAttributesK8sOpenapi>,
68 kube: Option<TypeHelperAttributesKube>,
72}
73
74#[derive(FromMeta)]
75pub(crate) struct TypeHelperAttributesK8sOpenapi {
79 metadata: Option<()>,
83 resource: Option<()>,
87}
88
89#[derive(FromMeta)]
90pub(crate) struct TypeHelperAttributesKube {
93 resource: Option<()>,
98}
99
100#[derive(FromMeta)]
101pub struct FieldAttributeToCopy {
105 pub attr: Path,
106 pub key: Option<Path>,
107}
108
109#[derive(FromDeriveInput, Debug, Clone)]
110#[darling(attributes(optionable))]
111pub struct CodegenSettings {
115 pub optionable_crate_name: Path,
119 pub ty_prefix: Option<Path>,
122 pub input_crate_replacement: Option<Ident>,
126}
127
128fn field_attr_copy_hashmap(
131 input: Vec<FieldAttributeToCopy>,
132 attr_kube: Option<&TypeHelperAttributesKube>,
133) -> HashMap<Path, HashSet<Path>> {
134 let mut result = HashMap::<Path, HashSet<Path>>::new();
135 for el in input {
136 if let Some(key) = el.key {
137 if let Some(entry) = result.get_mut(&el.attr) {
138 entry.insert(key);
139 } else {
140 result.insert(el.attr, HashSet::from([key]));
141 }
142 }
143 }
144 if attr_kube.is_some() {
145 let path_serde = Path::from_string("serde").unwrap();
146 let path_rename = Path::from_string("rename").unwrap();
147 let path_rename_all = Path::from_string("rename_all").unwrap();
148 let path_rename_all_fields = Path::from_string("rename_all_fields").unwrap();
149 if let Some(entry) = result.get_mut(&path_serde) {
150 if !entry.is_empty() {
152 entry.insert(path_rename);
153 entry.insert(path_rename_all);
154 }
155 } else {
156 result.insert(
157 path_serde,
158 HashSet::from([path_rename, path_rename_all, path_rename_all_fields]),
159 );
160 }
161 }
162 result
163}
164
165impl Default for CodegenSettings {
166 fn default() -> Self {
167 Self {
168 optionable_crate_name: parse_quote!(::optionable),
169 ty_prefix: None,
170 input_crate_replacement: None,
171 }
172 }
173}
174
175#[derive(FromAttributes)]
176#[darling(attributes(optionable))]
177struct FieldHelperAttributes {
179 required: Option<()>,
181}
182
183#[must_use]
185pub fn attribute_no_convert() -> Attribute {
186 parse_quote!(#[optionable(no_convert)])
187}
188
189#[must_use]
191pub fn attribute_suffix(suffix: &str) -> Attribute {
192 parse_quote!(#[optionable(suffix=#suffix)])
193}
194
195#[must_use]
198pub fn attribute_derives(derives: &PathList) -> Attribute {
199 parse_quote!(#[optionable(derive(#(#derives),*))])
200}
201
202#[allow(clippy::too_many_lines)]
208#[allow(clippy::items_after_statements)]
209pub fn derive_optionable(
210 input: DeriveInput,
211 settings: Option<&CodegenSettings>,
212) -> syn::Result<TokenStream> {
213 let attrs = TypeHelperAttributes::from_derive_input(&input)?;
214 error_missing_features(&attrs)?;
215 let k8s_resource_type = k8s_resource_type(&attrs)?;
216 let k8s_openapi_attrs = attrs.k8s_openapi.is_some().then(|| k8s_type_attr(&input));
217 let attr_copy_identifier = field_attr_copy_hashmap(attrs.attr_copy, attrs.kube.as_ref());
218
219 let mut derive = attrs
220 .derive
221 .into_iter()
222 .flat_map(|el| {
223 el.iter()
224 .map(|el| el.to_token_stream().to_string())
225 .collect::<Vec<_>>()
226 })
227 .collect::<BTreeSet<_>>();
228 if (attrs.k8s_openapi.is_some() || attrs.kube.is_some())
229 && let Some(k8s_derives) = k8s_derives(&input)
230 {
231 for el in k8s_derives {
232 derive.insert(el);
233 }
234 }
235 let settings = settings.map(Cow::Borrowed).unwrap_or_default();
236 let crate_name = &settings.optionable_crate_name;
237 let ty_attr_forwarded = forwarded_attributes(&input.attrs, &attr_copy_identifier)?;
238 let vis = input.vis;
239 let ty_ident_opt = {
240 let suffix = attrs.suffix.map_or_else(
241 || {
242 if attrs.kube.is_some() || attrs.k8s_openapi.is_some() {
243 Cow::Borrowed("Ac")
244 } else {
245 Cow::Borrowed("Opt")
246 }
247 },
248 |s| Cow::Owned(s.value()),
249 );
250 Ident::new(&(input.ident.to_string() + &suffix), input.ident.span())
251 };
252 let ty_ident = if let Some(mut ty_prefix) = settings.ty_prefix.clone() {
253 ty_prefix.segments.push(input.ident.into());
254 ty_prefix
255 } else {
256 input.ident.into()
257 };
258 let (impl_generics, ty_generics, _) = input.generics.split_for_impl();
259 let generics_colon = (!input.generics.params.is_empty()).then(|| quote! {::});
260
261 let skip_optionable_if_serde_serialize =
262 (attrs.k8s_openapi.is_some() || derive.iter().any(|el|is_serialize(el.as_str())))
264 .then(|| quote!(#[serde(skip_serializing_if = "Option::is_none")]));
265
266 struct Derived {
268 enum_struct: TokenStream,
270 fields: TokenStream,
272 where_clause_struct_enum: WhereClause,
274 where_clause_impl_optionable: WhereClause,
276 impl_optionable_convert: Option<TokenStream>,
278 }
279 let Derived {
280 enum_struct,
281 fields,
282 where_clause_struct_enum,
283 where_clause_impl_optionable,
284 impl_optionable_convert,
285 } = match input.data {
286 Data::Struct(s) => {
287 let mut struct_parsed = into_field_handling(
288 crate_name.to_owned(),
289 s.fields,
290 settings.input_crate_replacement.as_ref(),
291 )?;
292 k8s_adjust_fields(
293 &mut struct_parsed,
294 attrs.k8s_openapi.as_ref(),
295 attrs.kube.as_ref(),
296 k8s_resource_type.as_ref(),
297 crate_name,
298 &ty_ident_opt,
299 &ty_generics,
300 )?;
301 let WhereClauses {
302 struct_enum_def: where_clause_struct_enum,
303 impl_optionable: where_clause_impl_optionable,
304 impl_optionable_convert: where_clause_impl_optionable_convert,
305 } = where_clauses(
306 crate_name,
307 settings.input_crate_replacement.as_ref(),
308 &input.generics,
309 &derive,
310 attrs.no_convert.is_some(),
311 &struct_parsed.fields,
312 )?;
313 let unnamed_struct_semicolon =
314 (struct_parsed.struct_type == StructType::Unnamed).then(|| quote!(;));
315 let optioned_fields = optioned_fields(
316 &struct_parsed,
317 skip_optionable_if_serde_serialize.as_ref(),
318 &attr_copy_identifier,
319 )?;
320
321 let impl_optionable_convert = attrs.no_convert.is_none().then(|| {
322 let into_optioned_fields = into_optioned(&struct_parsed, |selector| quote! { self.#selector });
323 let try_from_optioned_fields =
324 try_from_optioned(&struct_parsed, |selector| quote! { value.#selector });
325 let merge_fields = merge_fields(
326 &struct_parsed,
327 |selector| quote! { self.#selector },
328 |selector| quote! { other.#selector },
329 true);
330 quote! {
331 #[automatically_derived]
332 impl #impl_generics #crate_name::OptionableConvert for #ty_ident #ty_generics #where_clause_impl_optionable_convert{
333 fn into_optioned(self) -> #ty_ident_opt #ty_generics {
334 #ty_ident_opt #generics_colon #ty_generics #into_optioned_fields
335 }
336
337 fn try_from_optioned(value: #ty_ident_opt #ty_generics) -> Result<Self, #crate_name::Error>{
338 Ok(Self #try_from_optioned_fields)
339 }
340
341 fn merge(&mut self, other: #ty_ident_opt #ty_generics) -> Result<(), #crate_name::Error>{
342 #merge_fields
343 Ok(())
344 }
345 }
346 }
347 });
348 Derived {
349 enum_struct: quote! {struct},
350 fields: quote! {#optioned_fields #unnamed_struct_semicolon},
351 where_clause_struct_enum,
352 where_clause_impl_optionable,
353 impl_optionable_convert,
354 }
355 }
356 Data::Enum(e) => {
357 let self_prefix = quote! {self_};
358 let other_prefix = quote! {other_};
359
360 let variants = e
361 .variants
362 .into_iter()
363 .map(|v| {
364 error_on_helper_attributes(&v.attrs, ERR_MSG_HELPER_ATTR_ENUM_VARIANTS)?;
365 let mut field_handling = into_field_handling(
366 crate_name.to_owned(),
367 v.fields,
368 settings.input_crate_replacement.as_ref(),
369 )?;
370 k8s_adjust_fields(
371 &mut field_handling,
372 attrs.k8s_openapi.as_ref(),
373 attrs.kube.as_ref(),
374 k8s_resource_type.as_ref(),
375 crate_name,
376 &ty_ident_opt,
377 &ty_generics,
378 )?;
379 Ok::<_, Error>((
380 v.ident,
381 forwarded_attributes(&v.attrs, &attr_copy_identifier)?,
382 field_handling,
383 ))
384 })
385 .collect::<Result<Vec<_>, _>>()?;
386 let all_fields = variants
387 .iter()
388 .flat_map(|(_, _, fields)| &fields.fields)
389 .collect::<Vec<_>>();
390 let WhereClauses {
391 struct_enum_def: where_clause_struct_enum,
392 impl_optionable: where_clause_impl_optionable,
393 impl_optionable_convert: where_clause_impl_optionable_convert,
394 } = where_clauses(
395 crate_name,
396 settings.input_crate_replacement.as_ref(),
397 &input.generics,
398 &derive,
399 attrs.no_convert.is_some(),
400 all_fields,
401 )?;
402
403 let optioned_variants = variants
404 .iter()
405 .map(|(variant, forward_attrs, f)| {
406 let fields = optioned_fields(
407 f,
408 skip_optionable_if_serde_serialize.as_ref(),
409 &attr_copy_identifier,
410 )?;
411 Ok::<_, Error>(quote!( #forward_attrs #variant #fields ))
412 })
413 .collect::<Result<Vec<_>, _>>()?;
414
415 let impl_optionable_convert = attrs.no_convert.is_none().then(|| {
416 let (into_variants, try_from_variants, merge_variants): (Vec<_>, Vec<_>, Vec<_>) = variants
417 .iter()
418 .map(|(variant, _, struct_parsed)| {
419 let fields_into = into_optioned(struct_parsed, |selector| {
420 format_ident!("{self_prefix}{selector}").to_token_stream()
421 });
422 let fields_try_from = try_from_optioned
423 (struct_parsed, |selector| {
424 format_ident!("{other_prefix}{selector}").to_token_stream()
425 });
426 let fields_merge = merge_fields(struct_parsed,
427 |selector| format_ident!("{self_prefix}{selector}").to_token_stream(),
428 |selector| format_ident!("{other_prefix}{selector}").to_token_stream(),
429 false);
430 let self_destructure = destructure(struct_parsed, &self_prefix)?;
431 let other_destructure = destructure(struct_parsed, &other_prefix)?;
432 Ok::<_, Error>((
433 quote!( Self::#variant #self_destructure => #ty_ident_opt::#variant #fields_into ),
434 quote!( #ty_ident_opt::#variant #other_destructure => Self::#variant #fields_try_from ),
435 quote!( #ty_ident_opt::#variant #other_destructure => {
436 if let Self::#variant #self_destructure = self {
437 #fields_merge
438 } else {
439 *self = Self::try_from_optioned(#ty_ident_opt::#variant #other_destructure)?;
440 }
441 })
442 ))
443 })
444 .collect::<Result<Vec<_>, _>>()?
445 .into_iter().multiunzip();
446 Ok::<_, Error>(quote! {
447 #[automatically_derived]
448 impl #impl_generics #crate_name::OptionableConvert for #ty_ident #ty_generics # where_clause_impl_optionable_convert {
449 fn into_optioned(self) -> #ty_ident_opt #ty_generics {
450 match self {
451 #(#into_variants),*
452 }
453 }
454
455 fn try_from_optioned(other: #ty_ident_opt #ty_generics)->Result<Self,#crate_name::Error>{
456 Ok(match other{
457 #(#try_from_variants),*
458 })
459 }
460
461 fn merge(&mut self, other: #ty_ident_opt #ty_generics) -> Result<(), #crate_name::Error>{
462 match other{
463 #(#merge_variants),*
464 }
465 Ok(())
466 }
467 }
468 })
469 }).transpose()?;
470 Derived {
471 enum_struct: quote! {enum},
472 fields: quote! {{#(#optioned_variants),*}},
473 where_clause_struct_enum,
474 where_clause_impl_optionable,
475 impl_optionable_convert,
476 }
477 }
478 Data::Union(_) => return error("#[derive(Optionable)] not supported for unit structs"),
479 };
480
481 let derive = derive
482 .into_iter()
483 .map(|derive| Path::from_string(&derive))
484 .collect::<Result<Vec<_>, _>>()?;
485 let derive = (!derive.is_empty()).then(|| quote! {#[derive(#(#derive),*)]});
486
487 let k8s_openapi_impl_resource = attrs
488 .k8s_openapi
489 .as_ref()
490 .is_some_and(|attr| attr.resource.is_some())
491 .then(|| {
492 k8s_openapi_impl_resource(
493 &ty_ident,
494 &ty_ident_opt,
495 &impl_generics,
496 &ty_generics,
497 &where_clause_impl_optionable,
498 )
499 });
500 let impl_k8s_metadata = attrs
501 .k8s_openapi
502 .as_ref()
503 .is_some_and(|attr| attr.metadata.is_some())
504 .then(|| {
505 k8s_openapi_impl_metadata(
506 &ty_ident,
507 &ty_ident_opt,
508 &impl_generics,
509 &ty_generics,
510 &where_clause_impl_optionable,
511 )
512 });
513 let kube_derive_resource = attrs
514 .kube
515 .is_some_and(|attr| attr.resource.is_some())
516 .then(|| {
517 quote! {
518 #[derive(kube::Resource)]
519 #[resource(inherit = #ty_ident )]
520 }
521 });
522 Ok(quote! {
523 #derive
524 #kube_derive_resource
525 #ty_attr_forwarded
526 #k8s_openapi_attrs
527 #vis #enum_struct #ty_ident_opt #impl_generics #where_clause_struct_enum #fields
528
529 #[automatically_derived]
530 impl #impl_generics #crate_name::Optionable for #ty_ident #ty_generics #where_clause_impl_optionable {
531 type Optioned = #ty_ident_opt #ty_generics;
532 }
533
534 #[automatically_derived]
535 impl #impl_generics #crate_name::Optionable for #ty_ident_opt #ty_generics #where_clause_impl_optionable {
536 type Optioned = #ty_ident_opt #ty_generics;
537 }
538
539 #impl_optionable_convert
540
541 #k8s_openapi_impl_resource
542 #impl_k8s_metadata
543 })
544}
545
546fn optioned_fields(
550 fields: &StructParsed,
551 serde_attributes: Option<&TokenStream>,
552 field_attr_forwards: &HashMap<Path, HashSet<Path>>,
553) -> Result<TokenStream, Error> {
554 let fields_token = fields.fields.iter().map(
555 |FieldParsed {
556 field: Field { attrs, vis, ident, ty, .. },
557 handling,
558 }| {
559 let forwarded_attrs = forwarded_attributes(attrs, field_attr_forwards)?;
560 let optioned_ty = optioned_ty(&fields.crate_name, ty);
561 let colon = ident.as_ref().map(|_| quote! {:});
562 Ok::<_, Error>(match handling {
563 FieldHandling::Required | FieldHandling::OptionedOnly => quote! {#forwarded_attrs #vis #ident #colon #ty},
564 FieldHandling::IsOption => quote! {#forwarded_attrs #serde_attributes #vis #ident #colon #optioned_ty},
565 FieldHandling::Other => quote! {#forwarded_attrs #serde_attributes #vis #ident #colon Option<#optioned_ty>},
566 })
567 },
568 ).collect::<Result<Vec<_>, _>>()?;
569 Ok(struct_wrapper(fields_token, &fields.struct_type))
570}
571
572fn into_optioned(
576 struct_parsed: &StructParsed,
577 self_selector_fn: impl Fn(&TokenStream) -> TokenStream,
578) -> TokenStream {
579 let fields_token = struct_parsed.fields.iter().enumerate().map(|(i, FieldParsed { field: Field { ident, ty, .. }, handling })| {
580 let colon = ident.as_ref().map(|_| quote! {:});
581 let selector = ident.as_ref().map_or_else(|| {
582 let i = Literal::usize_unsuffixed(i);
583 quote! {#i}
584 }, ToTokens::to_token_stream);
585 let self_selector = self_selector_fn(&selector);
586 let crate_name = &struct_parsed.crate_name;
587 match (handling, is_self_resolving_optioned(ty)) {
588 (FieldHandling::Required, _) | (FieldHandling::IsOption, true) => quote! {#ident #colon #self_selector},
589 (FieldHandling::IsOption, false) => quote! {#ident #colon #crate_name::OptionableConvert::into_optioned(#self_selector)},
590 (FieldHandling::Other, true) => quote! {#ident #colon Some(#self_selector)},
591 (FieldHandling::Other, false) => quote! {#ident #colon Some(#crate_name::OptionableConvert::into_optioned(#self_selector))},
592 (FieldHandling::OptionedOnly, _) => quote! {#ident #colon Default::default()},
593 }
594 });
595 struct_wrapper(fields_token, &struct_parsed.struct_type)
596}
597
598fn try_from_optioned(
602 struct_parsed: &StructParsed,
603 value_selector_fn: impl Fn(&TokenStream) -> TokenStream,
604) -> TokenStream {
605 let fields_token = struct_parsed.fields.iter().enumerate().map(|(i, FieldParsed { field: Field { ident, ty, .. }, handling })| {
606 let colon = ident.as_ref().map(|_| quote! {:});
607 let selector = ident.as_ref().map_or_else(|| {
608 let i = Literal::usize_unsuffixed(i);
609 quote! {#i}
610 }, ToTokens::to_token_stream);
611 let value_selector = value_selector_fn(&selector);
612 let crate_name = &struct_parsed.crate_name;
613 match (handling, is_self_resolving_optioned(ty)) {
614 (FieldHandling::Required, _) | (FieldHandling::IsOption, true) => quote! {#ident #colon value.#selector},
615 (FieldHandling::IsOption, false) => quote! {
616 #ident #colon #crate_name::OptionableConvert::try_from_optioned(
617 #value_selector
618 )?
619 },
620 (FieldHandling::Other, true) => {
621 let selector_quoted = LitStr::new(&selector.to_string(), ident.span());
622 quote! {
623 #ident #colon #value_selector.ok_or(#crate_name::Error{ missing_field: #selector_quoted })?
624 }
625 }
626 (FieldHandling::Other, false) => {
627 let selector_quoted = LitStr::new(&selector.to_string(), ident.span());
628 quote! {
629 #ident #colon #crate_name::OptionableConvert::try_from_optioned(#value_selector.ok_or(#crate_name::Error{ missing_field: #selector_quoted })?
630 )?
631 }
632 }
633 (FieldHandling::OptionedOnly, _) => TokenStream::default(),
634 }
635 });
636
637 struct_wrapper(fields_token, &struct_parsed.struct_type)
638}
639
640fn merge_fields(
646 struct_parsed: &StructParsed,
647 self_selector_fn: impl Fn(&TokenStream) -> TokenStream,
648 other_selector_fn: impl Fn(&TokenStream) -> TokenStream,
649 merge_self_mut: bool,
650) -> TokenStream {
651 let fields_token = struct_parsed.fields.iter().enumerate().map(
652 |(
653 i,
654 FieldParsed {
655 field: Field { ident, ty, .. },
656 handling,
657 },
658 )| {
659 let selector = ident.as_ref().map_or_else(
660 || {
661 let i = Literal::usize_unsuffixed(i);
662 quote! {#i}
663 },
664 ToTokens::to_token_stream,
665 );
666 let self_merge_mut_modifier = merge_self_mut.then(|| quote! {&mut});
667 let deref_modifier = (!merge_self_mut).then(|| quote! {*});
668 let self_selector = self_selector_fn(&selector);
669 let other_selector = other_selector_fn(&selector);
670 let crate_name = &struct_parsed.crate_name;
671 match (handling, is_self_resolving_optioned(ty)) {
672 (FieldHandling::Required, _) | (FieldHandling::IsOption, true) => quote! {#deref_modifier #self_selector = #other_selector;},
673 (FieldHandling::IsOption, false) => quote! {
674 #crate_name::OptionableConvert::merge(#self_merge_mut_modifier #self_selector, #other_selector)?;
675 },
676 (FieldHandling::Other, true) => quote! {
677 if let Some(other_value)=#other_selector{
678 #deref_modifier #self_selector = other_value;
679 }
680 },
681 (FieldHandling::Other, false) => quote! {
682 if let Some(other_value)=#other_selector{
683 #crate_name::OptionableConvert::merge(#self_merge_mut_modifier #self_selector, other_value)?;
684 }
685 },
686 (FieldHandling::OptionedOnly, _) => TokenStream::default(),
687 }
688 },
689 );
690
691 quote! {
692 #(#fields_token)*
693 }
694}
695fn optioned_ty(crate_name: &Path, ty: &Type) -> TokenStream {
700 if is_self_resolving_optioned(ty) {
701 ty.to_token_stream()
702 } else {
703 quote! { <#ty as #crate_name::Optionable>::Optioned }
704 }
705}
706
707const SELF_RESOLVING_TYPES: [&str; 18] = [
708 "i8", "i16", "i32", "i64", "i128", "isize", "u8", "u16", "u32", "u64", "u128", "usize", "f32",
710 "f64", "char", "bool", "String", "OsString",
712];
713
714fn is_self_resolving_optioned(ty: &Type) -> bool {
717 if let Type::Path(TypePath { qself, path }) = &ty
718 && qself.is_none()
719 && SELF_RESOLVING_TYPES.contains(&&*path.to_token_stream().to_string())
720 {
721 true
722 } else {
723 false
724 }
725}
726
727fn forwarded_attributes(
730 attrs: &[Attribute],
731 attr_to_copy: &HashMap<Path, HashSet<Path>>,
732) -> Result<Option<TokenStream>, Error> {
733 let forward_attrs = attrs
734 .iter()
735 .map(|attr| {
736 if attr.path().is_ident(HELPER_ATTR_IDENT) {
737 return match &attr.meta {
738 Meta::List(MetaList { tokens, .. }) => Ok(Some(quote!(#[#tokens]))),
739 _ => error("Only lists like `#[optionable_attr(Serialize,Deserialize)]` are supported for `optionable_attr`"),
740 };
741 }
742 let keys_to_copy=attr_to_copy.get(attr.path());
743 if let Some(keys_to_copy)=keys_to_copy{
744 if keys_to_copy.is_empty(){
746 if attr.path().is_ident("derive")&& let Meta::List(meta_list)=&attr.meta{
747 let derives=Punctuated::<Path, Token![,]>::parse_terminated
748 .parse2(meta_list.tokens.clone())?.into_iter().filter(|el|el.to_token_stream().to_string()!="Optionable"&&el.to_token_stream().to_string()!="optionable::Optionable");
749 return Ok(Some(quote! {#[derive(#(#derives,)*)]}));
750 }
751 Ok(Some(attr.to_token_stream()))
752 } else{
753 match &attr.meta{
754 Meta::Path(_) => Ok(None),
755 Meta::NameValue(meta_name_value) => Ok(keys_to_copy.contains(&meta_name_value.path).then(|| attr.to_token_stream())),
756 Meta::List(meta_list) => {
757 let inner_metas :Vec<TokenStream>= Punctuated::<Meta, Token![,]>::parse_terminated
759 .parse2(meta_list.tokens.clone())?.into_iter().filter_map(|meta|{
760 if let Meta::NameValue(meta_name_value)= meta{
761 keys_to_copy.contains(&meta_name_value.path).then(|| Ok::<_,Error>(meta_name_value.to_token_stream()))
762 } else{
763 None
764 }
765 }).collect::<Result<_,_>>()?;
766 if inner_metas.is_empty() {
767 Ok(None)
768 } else {
769 let mut attr=attr.clone();
770 let mut meta_list=meta_list.clone();
771 meta_list.tokens=inner_metas.into_iter().collect();
772 attr.meta=meta_list.into();
773 Ok(Some(attr.to_token_stream()))
774 }
775 }
776 }
777 }
778 } else{
779 Ok(None)
780 }
781 })
782 .collect::<Result<Vec<_>,_>>()?
783 .into_iter()
784 .flatten()
785 .collect::<TokenStream>();
786 Ok((!forward_attrs.is_empty()).then_some(forward_attrs))
787}
788
789#[cfg(test)]
790mod tests {
791 use crate::{derive_optionable, CodegenSettings};
792 use darling::FromMeta;
793 use proc_macro2::TokenStream;
794 use quote::quote;
795 use syn::{parse_quote, Path};
796
797 struct TestCase {
798 input: TokenStream,
799 output: TokenStream,
800 }
801
802 #[test]
803 #[allow(clippy::too_many_lines)]
804 fn test_optionable() {
805 let tcs = vec![
806 TestCase {
808 input: quote! {
809 #[derive(Optionable)]
810 struct DeriveExample {
811 name: String,
812 pub surname: String,
813 }
814 },
815 output: quote! {
816 struct DeriveExampleOpt {
817 name: Option<String>,
818 pub surname: Option<String>
819 }
820
821 #[automatically_derived]
822 impl ::optionable::Optionable for DeriveExample {
823 type Optioned = DeriveExampleOpt;
824 }
825
826 #[automatically_derived]
827 impl ::optionable::Optionable for DeriveExampleOpt {
828 type Optioned = DeriveExampleOpt;
829 }
830
831 #[automatically_derived]
832 impl ::optionable::OptionableConvert for DeriveExample {
833 fn into_optioned (self) -> DeriveExampleOpt {
834 DeriveExampleOpt {
835 name: Some(self.name),
836 surname:Some(self.surname)
837 }
838 }
839
840 fn try_from_optioned(value: DeriveExampleOpt ) -> Result <Self, ::optionable::Error> {
841 Ok(Self{
842 name: value.name.ok_or(::optionable::Error { missing_field: "name" })?,
843 surname: value.surname.ok_or(::optionable::Error { missing_field: "surname" })?
844 })
845 }
846
847 fn merge(&mut self, other: DeriveExampleOpt ) -> Result<(), ::optionable::Error> {
848 if let Some(other_value) = other.name {
849 self.name = other_value;
850 }
851 if let Some(other_value) = other.surname {
852 self.surname = other_value;
853 }
854 Ok(())
855 }
856 }
857 },
858 },
859 TestCase {
861 input: quote! {
862 #[derive(Optionable)]
863 #[optionable(no_convert)]
864 struct DeriveExample {
865 name: String,
866 pub surname: String,
867 }
868 },
869 output: quote! {
870 struct DeriveExampleOpt {
871 name: Option<String>,
872 pub surname: Option<String>
873 }
874
875 #[automatically_derived]
876 impl ::optionable::Optionable for DeriveExample {
877 type Optioned = DeriveExampleOpt;
878 }
879
880 #[automatically_derived]
881 impl ::optionable::Optionable for DeriveExampleOpt {
882 type Optioned = DeriveExampleOpt;
883 }
884 },
885 },
886 TestCase {
888 input: quote! {
889 #[derive(Optionable)]
890 struct DeriveExample {
891 name: String,
892 #[optionable(required)]
893 pub surname: String,
894 }
895 },
896 output: quote! {
897 struct DeriveExampleOpt {
898 name: Option<String>,
899 pub surname: String
900 }
901
902 #[automatically_derived]
903 impl ::optionable::Optionable for DeriveExample {
904 type Optioned = DeriveExampleOpt;
905 }
906
907 #[automatically_derived]
908 impl ::optionable::Optionable for DeriveExampleOpt {
909 type Optioned = DeriveExampleOpt;
910 }
911
912 #[automatically_derived]
913 impl ::optionable::OptionableConvert for DeriveExample {
914 fn into_optioned (self) -> DeriveExampleOpt {
915 DeriveExampleOpt {
916 name: Some(self.name),
917 surname: self.surname
918 }
919 }
920
921 fn try_from_optioned(value:DeriveExampleOpt ) -> Result <Self, ::optionable::Error> {
922 Ok (Self {
923 name: value.name.ok_or(::optionable::Error { missing_field: "name" })?,
924 surname: value.surname
925 })
926 }
927
928 fn merge(&mut self, other: DeriveExampleOpt ) -> Result<(), ::optionable::Error> {
929 if let Some(other_value) = other.name {
930 self.name = other_value;
931 }
932 self.surname = other.surname;
933 Ok (())
934 }
935 }
936 },
937 },
938 TestCase {
940 input: quote! {
941 #[derive(Optionable)]
942 #[optionable(derive(Deserialize,Serialize,Default),suffix="Ac")]
943 #[optionable(attr_copy(attr=serde,key=rename))]
944 #[optionable_attr(serde(rename_all_fields = "camelCase", deny_unknown_fields))]
945 #[optionable_attr(serde(default))]
946 struct DeriveExample {
947 #[optionable_attr(serde(rename = "firstName"))]
948 name: String,
949 #[serde(rename="middle__name")]
950 middle_name: Option<String>,
951 surname: String,
952 }
953 },
954 output: quote! {
955 #[derive(Default, Deserialize, Serialize)]
956 #[serde(rename_all_fields = "camelCase", deny_unknown_fields)]
957 #[serde(default)]
958 struct DeriveExampleAc {
959 #[serde(rename = "firstName")]
960 #[serde(skip_serializing_if = "Option::is_none")]
961 name: Option<String>,
962 #[serde(rename = "middle__name")]
963 #[serde(skip_serializing_if = "Option::is_none")]
964 middle_name: <Option<String> as ::optionable::Optionable>::Optioned,
965 #[serde(skip_serializing_if = "Option::is_none")]
966 surname: Option<String>
967 }
968
969 #[automatically_derived]
970 impl ::optionable::Optionable for DeriveExample {
971 type Optioned = DeriveExampleAc;
972 }
973
974 #[automatically_derived]
975 impl ::optionable::Optionable for DeriveExampleAc {
976 type Optioned = DeriveExampleAc;
977 }
978
979 #[automatically_derived]
980 impl ::optionable::OptionableConvert for DeriveExample {
981 fn into_optioned (self) -> DeriveExampleAc {
982 DeriveExampleAc {
983 name: Some(self.name),
984 middle_name: ::optionable::OptionableConvert::into_optioned(self.middle_name),
985 surname: Some(self.surname)
986 }
987 }
988
989 fn try_from_optioned(value: DeriveExampleAc ) -> Result <Self, ::optionable::Error> {
990 Ok(Self{
991 name: value.name.ok_or(::optionable::Error { missing_field: "name"})?,
992 middle_name: ::optionable::OptionableConvert::try_from_optioned(value.middle_name)?,
993 surname: value.surname.ok_or(::optionable::Error { missing_field: "surname"})?
994 })
995 }
996
997 fn merge(&mut self, other: DeriveExampleAc ) -> Result<(), ::optionable::Error> {
998 if let Some(other_value) = other.name {
999 self.name = other_value;
1000 }
1001 ::optionable::OptionableConvert::merge(&mut self.middle_name, other.middle_name)?;
1002 if let Some(other_value) = other.surname {
1003 self.surname = other_value;
1004 }
1005 Ok(())
1006 }
1007 }
1008 },
1009 },
1010 TestCase {
1012 input: quote! {
1013 #[derive(Optionable)]
1014 #[optionable(derive(serde::Deserialize,serde::Serialize),suffix="Ac")]
1015 struct DeriveExample {
1016 name: String,
1017 surname: String,
1018 }
1019 },
1020 output: quote! {
1021 #[derive(serde::Deserialize, serde::Serialize)]
1022 struct DeriveExampleAc {
1023 #[serde(skip_serializing_if = "Option::is_none")]
1024 name: Option<String>,
1025 #[serde(skip_serializing_if = "Option::is_none")]
1026 surname: Option<String>
1027 }
1028
1029 #[automatically_derived]
1030 impl ::optionable::Optionable for DeriveExample {
1031 type Optioned = DeriveExampleAc;
1032 }
1033
1034 #[automatically_derived]
1035 impl ::optionable::Optionable for DeriveExampleAc {
1036 type Optioned = DeriveExampleAc;
1037 }
1038
1039 #[automatically_derived]
1040 impl ::optionable::OptionableConvert for DeriveExample {
1041 fn into_optioned (self) -> DeriveExampleAc {
1042 DeriveExampleAc {
1043 name: Some(self.name),
1044 surname: Some(self.surname)
1045 }
1046 }
1047
1048 fn try_from_optioned(value: DeriveExampleAc ) -> Result <Self, ::optionable::Error> {
1049 Ok(Self{
1050 name: value.name.ok_or(::optionable::Error{ missing_field: "name"})?,
1051 surname: value.surname.ok_or(::optionable::Error{ missing_field: "surname"})?
1052 })
1053 }
1054
1055 fn merge(&mut self, other: DeriveExampleAc ) -> Result<(), ::optionable::Error> {
1056 if let Some(other_value) = other.name {
1057 self.name = other_value;
1058 }
1059 if let Some(other_value) = other.surname {
1060 self.surname = other_value;
1061 }
1062 Ok(())
1063 }
1064 }
1065 },
1066 },
1067 TestCase {
1069 input: quote! {
1070 #[derive(Optionable)]
1071 struct DeriveExample(pub String, i32);
1072 },
1073 output: quote! {
1074 struct DeriveExampleOpt(
1075 pub Option<String>,
1076 Option<i32>
1077 );
1078
1079 #[automatically_derived]
1080 impl ::optionable::Optionable for DeriveExample {
1081 type Optioned = DeriveExampleOpt;
1082 }
1083
1084 #[automatically_derived]
1085 impl ::optionable::Optionable for DeriveExampleOpt {
1086 type Optioned = DeriveExampleOpt;
1087 }
1088
1089 #[automatically_derived]
1090 impl ::optionable::OptionableConvert for DeriveExample {
1091 fn into_optioned (self) -> DeriveExampleOpt {
1092 DeriveExampleOpt (
1093 Some(self.0),
1094 Some(self.1)
1095 )
1096 }
1097
1098 fn try_from_optioned(value: DeriveExampleOpt ) -> Result <Self, ::optionable::Error> {
1099 Ok(Self(
1100 value.0.ok_or(::optionable::Error { missing_field:"0" })?,
1101 value.1.ok_or(::optionable::Error { missing_field: "1" })?
1102 ))
1103 }
1104
1105 fn merge(&mut self, other: DeriveExampleOpt ) -> Result<(), ::optionable::Error> {
1106 if let Some(other_value) = other.0 {
1107 self.0 = other_value;
1108 }
1109 if let Some (other_value) = other.1 {
1110 self.1 = other_value;
1111 }
1112 Ok (())
1113 }
1114 }
1115 },
1116 },
1117 TestCase {
1119 input: quote! {
1120 #[derive(Optionable)]
1121 struct DeriveExample(pub String, #[optionable(required)] i32);
1122 },
1123 output: quote! {
1124 struct DeriveExampleOpt(
1125 pub Option<String>,
1126 i32
1127 );
1128
1129 #[automatically_derived]
1130 impl ::optionable::Optionable for DeriveExample {
1131 type Optioned = DeriveExampleOpt;
1132 }
1133
1134 #[automatically_derived]
1135 impl ::optionable::Optionable for DeriveExampleOpt {
1136 type Optioned = DeriveExampleOpt;
1137 }
1138
1139 # [automatically_derived]
1140 impl ::optionable::OptionableConvert for DeriveExample {
1141 fn into_optioned (self) -> DeriveExampleOpt {
1142 DeriveExampleOpt (
1143 Some(self.0),
1144 self.1
1145 )
1146 }
1147
1148 fn try_from_optioned(value: DeriveExampleOpt ) -> Result <Self, ::optionable::Error> {
1149 Ok(Self(
1150 value.0.ok_or(::optionable::Error { missing_field: "0" })?,
1151 value.1))
1152 }
1153
1154 fn merge(&mut self, other: DeriveExampleOpt ) -> Result<(), ::optionable::Error> {
1155 if let Some(other_value) = other.0 {
1156 self.0 = other_value;
1157 }
1158 self.1 = other.1;
1159 Ok (())
1160 }
1161 }
1162 },
1163 },
1164 TestCase {
1166 input: quote! {
1167 #[derive(Optionable)]
1168 #[optionable(derive(Serialize, Deserialize))]
1169 struct DeriveExample<T, T2: Serialize, T3> {
1170 output: T,
1171 input: T2,
1172 #[optionable(required)]
1173 extra: T3,
1174 }
1175 },
1176 output: quote! {
1177 #[derive (Deserialize, Serialize)]
1178 struct DeriveExampleOpt<T, T2: Serialize, T3>
1179 where T: ::optionable::Optionable,
1180 <T as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize,
1181 T2: ::optionable::Optionable,
1182 <T2 as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize
1183 {
1184 #[serde(skip_serializing_if="Option::is_none")]
1185 output: Option< <T as ::optionable::Optionable>::Optioned>,
1186 #[serde(skip_serializing_if="Option::is_none")]
1187 input: Option< <T2 as ::optionable::Optionable>::Optioned>,
1188 extra: T3
1189 }
1190
1191 #[automatically_derived]
1192 impl<T, T2: Serialize, T3> ::optionable::Optionable for DeriveExample<T, T2, T3>
1193 where T: ::optionable::Optionable,
1194 <T as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize,
1195 T2: ::optionable::Optionable,
1196 <T2 as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize
1197 {
1198 type Optioned = DeriveExampleOpt<T,T2,T3>;
1199 }
1200
1201 #[automatically_derived]
1202 impl<T, T2: Serialize, T3> ::optionable::Optionable for DeriveExampleOpt<T, T2, T3>
1203 where T: ::optionable::Optionable,
1204 <T as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize,
1205 T2: ::optionable::Optionable,
1206 <T2 as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize
1207 {
1208 type Optioned = DeriveExampleOpt<T, T2, T3>;
1209 }
1210
1211 #[automatically_derived]
1212 impl <T, T2:Serialize, T3> ::optionable::OptionableConvert for DeriveExample<T, T2, T3>
1213 where T: ::optionable::OptionableConvert,
1214 <T as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize,
1215 T2: ::optionable::OptionableConvert,
1216 <T2 as ::optionable::Optionable>::Optioned: Sized + serde::de::DeserializeOwned + Serialize
1217 {
1218 fn into_optioned (self) -> DeriveExampleOpt<T, T2, T3> {
1219 DeriveExampleOpt::<T, T2, T3> {
1220 output: Some(::optionable::OptionableConvert::into_optioned(self.output)),
1221 input: Some(::optionable::OptionableConvert::into_optioned(self.input)),
1222 extra: self.extra
1223 }
1224 }
1225
1226 fn try_from_optioned(value: DeriveExampleOpt<T, T2, T3> ) -> Result <Self, ::optionable::Error> {
1227 Ok(Self{
1228 output: ::optionable::OptionableConvert::try_from_optioned(value.output.ok_or(::optionable::Error { missing_field: "output" })?)?,
1229 input: ::optionable::OptionableConvert::try_from_optioned(value.input.ok_or(::optionable::Error { missing_field: "input" })?)?,
1230 extra: value.extra
1231 })
1232 }
1233
1234 fn merge(&mut self, other: DeriveExampleOpt<T, T2, T3> ) -> Result<(), ::optionable::Error> {
1235 if let Some(other_value) = other.output {
1236 ::optionable::OptionableConvert::merge(&mut self.output, other_value)?;
1237 }
1238 if let Some(other_value) = other.input {
1239 ::optionable::OptionableConvert::merge(&mut self.input, other_value)?;
1240 }
1241 self.extra=other.extra;
1242 Ok(())
1243 }
1244 }
1245 },
1246 },
1247 TestCase {
1248 input: quote! {
1249 #[derive(Optionable)]
1250 enum DeriveExample {
1251 Unit,
1252 Plain(String),
1253 Address{street: String, number: u32},
1254 Address2(String,u32),
1255 }
1256 },
1257 output: quote! {
1258 enum DeriveExampleOpt {
1259 Unit,
1260 Plain( Option<String> ),
1261 Address{ street: Option<String>, number:Option<u32> },
1262 Address2( Option<String>, Option<u32> )
1263 }
1264
1265 #[automatically_derived]
1266 impl ::optionable::Optionable for DeriveExample {
1267 type Optioned = DeriveExampleOpt;
1268 }
1269
1270 #[automatically_derived]
1271 impl ::optionable::Optionable for DeriveExampleOpt {
1272 type Optioned = DeriveExampleOpt;
1273 }
1274
1275 #[automatically_derived]
1276 impl ::optionable::OptionableConvert for DeriveExample {
1277 fn into_optioned (self) -> DeriveExampleOpt {
1278 match self{
1279 Self::Unit => DeriveExampleOpt::Unit,
1280 Self::Plain(self_0) => DeriveExampleOpt::Plain(
1281 Some(self_0)
1282 ),
1283 Self::Address{street: self_street, number: self_number} => DeriveExampleOpt::Address{
1284 street: Some(self_street),
1285 number: Some(self_number)
1286 },
1287 Self::Address2(self_0, self_1) => DeriveExampleOpt::Address2(
1288 Some(self_0),
1289 Some(self_1)
1290 )
1291 }
1292 }
1293
1294 fn try_from_optioned(other: DeriveExampleOpt) -> Result <Self, ::optionable::Error> {
1295 Ok (match other {
1296 DeriveExampleOpt::Unit => Self::Unit,
1297 DeriveExampleOpt::Plain(other_0) => Self::Plain(
1298 other_0.ok_or(::optionable::Error { missing_field: "0" })?
1299 ),
1300 DeriveExampleOpt::Address{street: other_street, number: other_number} => Self::Address{
1301 street: other_street.ok_or(::optionable::Error { missing_field: "street" })?,
1302 number: other_number.ok_or(::optionable::Error { missing_field: "number" })?
1303 },
1304 DeriveExampleOpt::Address2(other_0, other_1) => Self::Address2(
1305 other_0.ok_or(::optionable::Error { missing_field: "0"})?,
1306 other_1.ok_or(::optionable::Error { missing_field: "1"})?)
1307 })
1308 }
1309
1310 fn merge(&mut self, other: DeriveExampleOpt) -> Result<(), ::optionable::Error> {
1311 match other {
1312 DeriveExampleOpt::Unit => {
1313 if let Self::Unit = self {} else {
1314 *self = Self::try_from_optioned(DeriveExampleOpt::Unit)?;
1315 }
1316 },
1317 DeriveExampleOpt::Plain(other_0) => {
1318 if let Self::Plain(self_0) = self{
1319 if let Some(other_value) = other_0 {
1320 *self_0 = other_value;
1321 }
1322 } else {
1323 *self = Self::try_from_optioned(DeriveExampleOpt::Plain(other_0))?;
1324 }
1325 },
1326 DeriveExampleOpt::Address{street: other_street, number: other_number} => {
1327 if let Self::Address{street: self_street, number: self_number} = self{
1328 if let Some(other_value) = other_street {
1329 *self_street = other_value;
1330 }
1331 if let Some(other_value) = other_number {
1332 *self_number = other_value;
1333 }
1334 } else {
1335 *self = Self::try_from_optioned(DeriveExampleOpt::Address{street: other_street, number: other_number})?;
1336 }
1337 },
1338 DeriveExampleOpt::Address2(other_0, other_1) => {
1339 if let Self::Address2(self_0, self_1) = self{
1340 if let Some(other_value) = other_0 {
1341 *self_0 = other_value;
1342 }
1343 if let Some(other_value) = other_1 {
1344 *self_1 = other_value;
1345 }
1346 } else {
1347 *self = Self::try_from_optioned(DeriveExampleOpt::Address2(other_0, other_1))?;
1348 }
1349 }
1350 }
1351 Ok(())
1352 }
1353 }
1354 },
1355 },
1356 ];
1357 for tc in tcs {
1358 let input = syn::parse2(tc.input).unwrap();
1359 let output = derive_optionable(input, None).unwrap();
1360 println!("{output}");
1361 assert_eq!(tc.output.to_string(), output.to_string());
1362 }
1363 }
1364
1365 #[test]
1366 #[allow(clippy::too_many_lines)]
1367 fn test_crate_replacement() {
1369 let tcs = vec![TestCase {
1370 input: quote! {
1371 #[derive(Optionable)]
1372 struct DeriveExample {
1373 name: crate::Name,
1374 pub surname: Box<crate::SurName>,
1375 }
1376 },
1377 output: quote! {
1378 struct DeriveExampleOpt {
1379 name: Option< <::testcrate::Name as crate::Optionable>::Optioned>,
1380 pub surname: Option< <Box<::testcrate::SurName> as crate::Optionable>::Optioned>
1381 }
1382
1383 #[automatically_derived]
1384 impl crate::Optionable for crate_prefix::DeriveExample {
1385 type Optioned = DeriveExampleOpt;
1386 }
1387
1388 #[automatically_derived]
1389 impl crate::Optionable for DeriveExampleOpt {
1390 type Optioned = DeriveExampleOpt;
1391 }
1392
1393 #[automatically_derived]
1394 impl crate::OptionableConvert for crate_prefix::DeriveExample {
1395 fn into_optioned (self) -> DeriveExampleOpt {
1396 DeriveExampleOpt {
1397 name: Some(crate::OptionableConvert::into_optioned(self.name)),
1398 surname: Some(crate::OptionableConvert::into_optioned(self.surname))
1399 }
1400 }
1401
1402 fn try_from_optioned(value: DeriveExampleOpt ) -> Result <Self, crate::Error> {
1403 Ok(Self{
1404 name: crate::OptionableConvert::try_from_optioned(value.name.ok_or(crate::Error { missing_field: "name" })?)?,
1405 surname: crate::OptionableConvert::try_from_optioned(value.surname.ok_or(crate::Error { missing_field: "surname" })?)?
1406 })
1407 }
1408
1409 fn merge(&mut self, other: DeriveExampleOpt ) -> Result<(), crate::Error> {
1410 if let Some(other_value) = other.name {
1411 crate::OptionableConvert::merge(&mut self.name, other_value)?;
1412 }
1413 if let Some(other_value) = other.surname {
1414 crate::OptionableConvert::merge(&mut self.surname, other_value)?;
1415 }
1416 Ok(())
1417 }
1418 }
1419 },
1420 }];
1421 for tc in tcs {
1422 let input = syn::parse2(tc.input).unwrap();
1423 let output = derive_optionable(
1424 input,
1425 Some(&CodegenSettings {
1426 ty_prefix: Some(Path::from_string("crate_prefix").unwrap()),
1427 optionable_crate_name: Path::from_string("crate").unwrap(),
1428 input_crate_replacement: Some(parse_quote!(testcrate)),
1429 }),
1430 )
1431 .unwrap();
1432 assert_eq!(tc.output.to_string(), output.to_string());
1433 }
1434 }
1435}