@@ -4,10 +4,10 @@ use darling::{FromField, Result, util::IdentString};
4
4
use k8s_version:: Version ;
5
5
use proc_macro2:: TokenStream ;
6
6
use quote:: quote;
7
- use syn:: { Attribute , Field , Ident , Type } ;
7
+ use syn:: { Attribute , Field , Ident , Path , Type } ;
8
8
9
9
use crate :: {
10
- attrs:: item:: FieldAttributes ,
10
+ attrs:: item:: { FieldAttributes , Hint } ,
11
11
codegen:: {
12
12
Direction , VersionDefinition ,
13
13
changes:: { BTreeMapExt , ChangesetExt } ,
@@ -21,6 +21,7 @@ pub struct VersionedField {
21
21
pub original_attributes : Vec < Attribute > ,
22
22
pub changes : Option < BTreeMap < Version , ItemStatus > > ,
23
23
pub idents : FieldIdents ,
24
+ pub hint : Option < Hint > ,
24
25
pub nested : bool ,
25
26
pub ty : Type ,
26
27
}
@@ -47,6 +48,7 @@ impl VersionedField {
47
48
48
49
Ok ( Self {
49
50
original_attributes : field_attributes. attrs ,
51
+ hint : field_attributes. hint ,
50
52
ty : field. ty ,
51
53
changes,
52
54
idents,
@@ -60,19 +62,31 @@ impl VersionedField {
60
62
}
61
63
}
62
64
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
+ /// ```
63
80
pub fn generate_for_container ( & self , version : & VersionDefinition ) -> Option < TokenStream > {
64
81
let original_attributes = & self . original_attributes ;
65
82
66
83
match & self . changes {
67
84
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).
76
90
let field_type = & self . ty ;
77
91
78
92
// NOTE (@Techassi): https://rust-lang.github.io/rust-clippy/master/index.html#/expect_fun_call
@@ -97,14 +111,12 @@ impl VersionedField {
97
111
note,
98
112
..
99
113
} => {
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.
108
120
let deprecated_attr = if let Some ( note) = note {
109
121
quote ! { #[ deprecated = #note] }
110
122
} else {
@@ -124,8 +136,7 @@ impl VersionedField {
124
136
ty,
125
137
..
126
138
} => {
127
- // TODO (@Techassi): Also carry along the deprecation
128
- // note.
139
+ // TODO (@Techassi): Also carry along the deprecation note.
129
140
let deprecated_attr = previously_deprecated. then ( || quote ! { #[ deprecated] } ) ;
130
141
131
142
Some ( quote ! {
@@ -137,8 +148,8 @@ impl VersionedField {
137
148
}
138
149
}
139
150
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.
142
153
let field_ident = & self . idents . original ;
143
154
let field_type = & self . ty ;
144
155
@@ -150,6 +161,27 @@ impl VersionedField {
150
161
}
151
162
}
152
163
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
+ /// ```
153
185
pub fn generate_for_from_impl (
154
186
& self ,
155
187
direction : Direction ,
@@ -163,9 +195,9 @@ impl VersionedField {
163
195
let change = changes. get_expect ( & version. inner ) ;
164
196
165
197
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.
169
201
( ItemStatus :: NotPresent , ItemStatus :: NotPresent ) => None ,
170
202
(
171
203
_,
@@ -186,93 +218,57 @@ impl VersionedField {
186
218
..
187
219
} ,
188
220
) => 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
+ ) ) ,
229
233
} ,
230
234
( old, next) => {
231
235
let next_field_ident = next. get_ident ( ) ;
232
236
let old_field_ident = old. get_ident ( ) ;
233
237
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.
237
240
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
+ ) ) ,
254
253
}
255
254
}
256
255
}
257
256
}
258
257
None => {
259
258
let field_ident = & self . idents . original ;
260
259
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
+ ) )
272
266
}
273
267
}
274
268
}
275
269
270
+ /// Generates code needed when a tracked conversion for this field needs to be inserted into the
271
+ /// status.
276
272
pub fn generate_for_status_insertion (
277
273
& self ,
278
274
direction : Direction ,
@@ -315,6 +311,8 @@ impl VersionedField {
315
311
}
316
312
}
317
313
314
+ /// Generates code needed when a tracked conversion for this field needs to be removed from the
315
+ /// status.
318
316
pub fn generate_for_status_removal (
319
317
& self ,
320
318
direction : Direction ,
@@ -389,6 +387,66 @@ impl VersionedField {
389
387
}
390
388
}
391
389
}
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
+ }
392
450
}
393
451
394
452
#[ derive( Debug ) ]
0 commit comments