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