@@ -4,10 +4,10 @@ use darling::{FromField, Result, util::IdentString};
44use k8s_version:: Version ;
55use proc_macro2:: TokenStream ;
66use quote:: quote;
7- use syn:: { Attribute , Field , Ident , Type } ;
7+ use syn:: { Attribute , Field , Ident , Path , Type } ;
88
99use crate :: {
10- attrs:: item:: FieldAttributes ,
10+ attrs:: item:: { FieldAttributes , Hint } ,
1111 codegen:: {
1212 Direction , VersionDefinition ,
1313 changes:: { BTreeMapExt , ChangesetExt } ,
@@ -21,6 +21,7 @@ pub struct VersionedField {
2121 pub original_attributes : Vec < Attribute > ,
2222 pub changes : Option < BTreeMap < Version , ItemStatus > > ,
2323 pub idents : FieldIdents ,
24+ pub hint : Option < Hint > ,
2425 pub nested : bool ,
2526 pub ty : Type ,
2627}
@@ -47,6 +48,7 @@ impl VersionedField {
4748
4849 Ok ( Self {
4950 original_attributes : field_attributes. attrs ,
51+ hint : field_attributes. hint ,
5052 ty : field. ty ,
5153 changes,
5254 idents,
@@ -60,19 +62,31 @@ impl VersionedField {
6062 }
6163 }
6264
65+ /// Generates field definitions for the use inside container (struct) definitions.
66+ ///
67+ /// This function needs to take into account multiple conditions:
68+ ///
69+ /// - Only emit the field if it exists for the currently generated version.
70+ /// - Emit field with new name and type if there was a name and/or type change.
71+ /// - Handle deprecated fields accordingly.
72+ ///
73+ /// ### Example
74+ ///
75+ /// ```ignore
76+ /// struct Foo {
77+ /// bar: usize, // < This functions generates one or more of these definitions
78+ /// }
79+ /// ```
6380 pub fn generate_for_container ( & self , version : & VersionDefinition ) -> Option < TokenStream > {
6481 let original_attributes = & self . original_attributes ;
6582
6683 match & self . changes {
6784 Some ( changes) => {
68- // Check if the provided container version is present in the map
69- // of actions. If it is, some action occurred in exactly that
70- // version and thus code is generated for that field based on
71- // the type of action.
72- // If not, the provided version has no action attached to it.
73- // The code generation then depends on the relation to other
74- // versions (with actions).
75-
85+ // Check if the provided container version is present in the map of actions. If it
86+ // is, some action occurred in exactly that version and thus code is generated for
87+ // that field based on the type of action.
88+ // If not, the provided version has no action attached to it. The code generation
89+ // then depends on the relation to other versions (with actions).
7690 let field_type = & self . ty ;
7791
7892 // NOTE (@Techassi): https://rust-lang.github.io/rust-clippy/master/index.html#/expect_fun_call
@@ -97,14 +111,12 @@ impl VersionedField {
97111 note,
98112 ..
99113 } => {
100- // FIXME (@Techassi): Emitting the deprecated attribute
101- // should cary over even when the item status is
102- // 'NoChange'.
103- // TODO (@Techassi): Make the generation of deprecated
104- // items customizable. When a container is used as a K8s
105- // CRD, the item must continue to exist, even when
106- // deprecated. For other versioning use-cases, that
107- // might not be the case.
114+ // FIXME (@Techassi): Emitting the deprecated attribute should cary over even
115+ // when the item status is 'NoChange'.
116+ // TODO (@Techassi): Make the generation of deprecated items customizable.
117+ // When a container is used as a K8s CRD, the item must continue to exist,
118+ // even when deprecated. For other versioning use-cases, that might not be
119+ // the case.
108120 let deprecated_attr = if let Some ( note) = note {
109121 quote ! { #[ deprecated = #note] }
110122 } else {
@@ -124,8 +136,7 @@ impl VersionedField {
124136 ty,
125137 ..
126138 } => {
127- // TODO (@Techassi): Also carry along the deprecation
128- // note.
139+ // TODO (@Techassi): Also carry along the deprecation note.
129140 let deprecated_attr = previously_deprecated. then ( || quote ! { #[ deprecated] } ) ;
130141
131142 Some ( quote ! {
@@ -137,8 +148,8 @@ impl VersionedField {
137148 }
138149 }
139150 None => {
140- // If there is no chain of field actions, the field is not
141- // versioned and therefore included in all versions.
151+ // If there is no chain of field actions, the field is not versioned and therefore
152+ // included in all versions.
142153 let field_ident = & self . idents . original ;
143154 let field_type = & self . ty ;
144155
@@ -150,6 +161,27 @@ impl VersionedField {
150161 }
151162 }
152163
164+ /// Generates field definitions for the use inside `From` impl blocks.
165+ ///
166+ /// This function needs to take into account multiple conditions:
167+ ///
168+ /// - Only emit the field if it exists for the currently generated version.
169+ /// - Emit fields which previously didn't exist with the correct initialization function.
170+ /// - Emit field with new name and type if there was a name and/or type change.
171+ /// - Handle tracking conversions without data-loss.
172+ /// - Handle deprecated fields accordingly.
173+ ///
174+ /// ### Example
175+ ///
176+ /// ```ignore
177+ /// impl From<v1alpha1::Foo> for v1alpha2::Foo {
178+ /// fn from(value: v1alpha1::Foo) -> Self {
179+ /// Self {
180+ /// bar: value.bar, // < This functions generates one or more of these definitions
181+ /// }
182+ /// }
183+ /// }
184+ /// ```
153185 pub fn generate_for_from_impl (
154186 & self ,
155187 direction : Direction ,
@@ -163,9 +195,9 @@ impl VersionedField {
163195 let change = changes. get_expect ( & version. inner ) ;
164196
165197 match ( change, next_change) {
166- // If both this status and the next one is NotPresent, which means
167- // a field was introduced after a bunch of versions, we don't
168- // need to generate any code for the From impl.
198+ // If both this status and the next one is NotPresent, which means a field was
199+ // introduced after a bunch of versions, we don't need to generate any code for
200+ // the From impl.
169201 ( ItemStatus :: NotPresent , ItemStatus :: NotPresent ) => None ,
170202 (
171203 _,
@@ -186,93 +218,57 @@ impl VersionedField {
186218 ..
187219 } ,
188220 ) => match direction {
189- Direction :: Upgrade => match upgrade_with {
190- // The user specified a custom conversion function which
191- // will be used here instead of the default .into() call
192- // which utilizes From impls.
193- Some ( upgrade_fn) => Some ( quote ! {
194- #to_ident: #upgrade_fn( #from_struct_ident. #from_ident) ,
195- } ) ,
196- // Default .into() call using From impls.
197- None => {
198- if self . nested {
199- let json_path_ident = to_ident. json_path_ident ( ) ;
200-
201- Some ( quote ! {
202- #to_ident: #from_struct_ident. #from_ident. tracking_into( status, & #json_path_ident) ,
203- } )
204- } else {
205- Some ( quote ! {
206- #to_ident: #from_struct_ident. #from_ident. into( ) ,
207- } )
208- }
209- }
210- } ,
211- Direction :: Downgrade => match downgrade_with {
212- Some ( downgrade_fn) => Some ( quote ! {
213- #from_ident: #downgrade_fn( #from_struct_ident. #to_ident) ,
214- } ) ,
215- None => {
216- if self . nested {
217- let json_path_ident = from_ident. json_path_ident ( ) ;
218-
219- Some ( quote ! {
220- #from_ident: #from_struct_ident. #to_ident. tracking_into( status, & #json_path_ident) ,
221- } )
222- } else {
223- Some ( quote ! {
224- #from_ident: #from_struct_ident. #to_ident. into( ) ,
225- } )
226- }
227- }
228- } ,
221+ Direction :: Upgrade => Some ( self . generate_from_impl_field (
222+ to_ident,
223+ from_struct_ident,
224+ from_ident,
225+ upgrade_with. as_ref ( ) ,
226+ ) ) ,
227+ Direction :: Downgrade => Some ( self . generate_from_impl_field (
228+ from_ident,
229+ from_struct_ident,
230+ to_ident,
231+ downgrade_with. as_ref ( ) ,
232+ ) ) ,
229233 } ,
230234 ( old, next) => {
231235 let next_field_ident = next. get_ident ( ) ;
232236 let old_field_ident = old. get_ident ( ) ;
233237
234- // NOTE (@Techassi): Do we really need .into() here. I'm
235- // currently not sure why it is there and if it is needed
236- // in some edge cases.
238+ // NOTE (@Techassi): Do we really need .into() here. I'm currently not sure
239+ // why it is there and if it is needed in some edge cases.
237240 match direction {
238- Direction :: Upgrade => {
239- if self . nested {
240- let json_path_ident = next_field_ident. json_path_ident ( ) ;
241-
242- Some ( quote ! {
243- #next_field_ident: #from_struct_ident. #old_field_ident. tracking_into( status, & #json_path_ident) ,
244- } )
245- } else {
246- Some ( quote ! {
247- #next_field_ident: #from_struct_ident. #old_field_ident. into( ) ,
248- } )
249- }
250- }
251- Direction :: Downgrade => Some ( quote ! {
252- #old_field_ident: #from_struct_ident. #next_field_ident. into( ) ,
253- } ) ,
241+ Direction :: Upgrade => Some ( self . generate_from_impl_field (
242+ next_field_ident,
243+ from_struct_ident,
244+ old_field_ident,
245+ None ,
246+ ) ) ,
247+ Direction :: Downgrade => Some ( self . generate_from_impl_field (
248+ old_field_ident,
249+ from_struct_ident,
250+ next_field_ident,
251+ None ,
252+ ) ) ,
254253 }
255254 }
256255 }
257256 }
258257 None => {
259258 let field_ident = & self . idents . original ;
260259
261- if self . nested {
262- let json_path_ident = field_ident. json_path_ident ( ) ;
263-
264- Some ( quote ! {
265- #field_ident: #from_struct_ident. #field_ident. tracking_into( status, & #json_path_ident) ,
266- } )
267- } else {
268- Some ( quote ! {
269- #field_ident: #from_struct_ident. #field_ident. into( ) ,
270- } )
271- }
260+ Some ( self . generate_from_impl_field (
261+ field_ident,
262+ from_struct_ident,
263+ field_ident,
264+ None ,
265+ ) )
272266 }
273267 }
274268 }
275269
270+ /// Generates code needed when a tracked conversion for this field needs to be inserted into the
271+ /// status.
276272 pub fn generate_for_status_insertion (
277273 & self ,
278274 direction : Direction ,
@@ -315,6 +311,8 @@ impl VersionedField {
315311 }
316312 }
317313
314+ /// Generates code needed when a tracked conversion for this field needs to be removed from the
315+ /// status.
318316 pub fn generate_for_status_removal (
319317 & self ,
320318 direction : Direction ,
@@ -389,6 +387,66 @@ impl VersionedField {
389387 }
390388 }
391389 }
390+
391+ /// Generates field definitions to be used inside `From` impl blocks.
392+ fn generate_from_impl_field (
393+ & self ,
394+ lhs_field_ident : & IdentString ,
395+ rhs_struct_ident : & IdentString ,
396+ rhs_field_ident : & IdentString ,
397+ custom_conversion_function : Option < & Path > ,
398+ ) -> TokenStream {
399+ match custom_conversion_function {
400+ // The user specified a custom conversion function which will be used here instead of the
401+ // default conversion call which utilizes From impls.
402+ Some ( convert_fn) => quote ! {
403+ #lhs_field_ident: #convert_fn( #rhs_struct_ident. #rhs_field_ident) ,
404+ } ,
405+ // Default conversion call using From impls.
406+ None => {
407+ if self . nested {
408+ let json_path_ident = lhs_field_ident. json_path_ident ( ) ;
409+ let func = self . generate_tracking_conversion_function ( json_path_ident) ;
410+
411+ quote ! {
412+ #lhs_field_ident: #rhs_struct_ident. #rhs_field_ident. #func,
413+ }
414+ } else {
415+ let func = self . generate_conversion_function ( ) ;
416+
417+ quote ! {
418+ #lhs_field_ident: #rhs_struct_ident. #rhs_field_ident. #func,
419+ }
420+ }
421+ }
422+ }
423+ }
424+
425+ /// Generates tracking conversion functions used by field definitions in `From` impl blocks.
426+ fn generate_tracking_conversion_function ( & self , json_path_ident : IdentString ) -> TokenStream {
427+ match & self . hint {
428+ Some ( hint) => match hint {
429+ Hint :: Option => {
430+ quote ! { map( |v| v. tracking_into( status, & #json_path_ident) ) }
431+ }
432+ Hint :: Vec => {
433+ quote ! { into_iter( ) . map( |v| v. tracking_into( status, & #json_path_ident) ) . collect( ) }
434+ }
435+ } ,
436+ None => quote ! { tracking_into( status, & #json_path_ident) } ,
437+ }
438+ }
439+
440+ /// Generates conversion functions used by field definitions in `From` impl blocks.
441+ fn generate_conversion_function ( & self ) -> TokenStream {
442+ match & self . hint {
443+ Some ( hint) => match hint {
444+ Hint :: Option => quote ! { map( Into :: into) } ,
445+ Hint :: Vec => quote ! { into_iter( ) . map( Into :: into) . collect( ) } ,
446+ } ,
447+ None => quote ! { into( ) } ,
448+ }
449+ }
392450}
393451
394452#[ derive( Debug ) ]
0 commit comments