@@ -129,6 +129,7 @@ impl Struct {
129
129
let variant_data_ident = & self . common . idents . kubernetes_parameter ;
130
130
let version_enum_ident = & self . common . idents . kubernetes_version ;
131
131
let enum_ident = & self . common . idents . kubernetes ;
132
+ let enum_ident_string = enum_ident. to_string ( ) ;
132
133
133
134
// Only add the #[automatically_derived] attribute if this impl is used outside of a
134
135
// module (in standalone mode).
@@ -153,7 +154,8 @@ impl Struct {
153
154
// Generate additional Kubernetes code, this is split out to reduce the complexity in this
154
155
// function.
155
156
let status_struct = self . generate_kubernetes_status_struct ( kubernetes_arguments, is_nested) ;
156
- let version_enum = self . generate_kubernetes_version_enum ( tokens, vis, is_nested) ;
157
+ let version_enum =
158
+ self . generate_kubernetes_version_enum ( kubernetes_arguments, tokens, vis, is_nested) ;
157
159
let convert_method = self . generate_kubernetes_conversion ( versions) ;
158
160
159
161
let parse_object_error = quote ! { #versioned_path:: ParseObjectError } ;
@@ -173,21 +175,39 @@ impl Struct {
173
175
#k8s_openapi_path:: apiextensions_apiserver:: pkg:: apis:: apiextensions:: v1:: CustomResourceDefinition ,
174
176
#kube_core_path:: crd:: MergeError >
175
177
{
176
- #kube_core_path:: crd:: merge_crds( vec![ #( #crd_fns) , * ] , stored_apiversion. as_str ( ) )
178
+ #kube_core_path:: crd:: merge_crds( vec![ #( #crd_fns) , * ] , stored_apiversion. as_version_str ( ) )
177
179
}
178
180
179
181
#convert_method
180
182
181
- fn from_json_value( value: #serde_json_path:: Value ) -> :: std:: result:: Result <Self , #parse_object_error> {
182
- let api_version = value
183
+ fn from_json_object( object_value: #serde_json_path:: Value ) -> :: std:: result:: Result <Self , #parse_object_error> {
184
+ let object_kind = object_value
185
+ . get( "kind" )
186
+ . ok_or_else( || #parse_object_error:: FieldMissing { field: "kind" . to_owned( ) } ) ?
187
+ . as_str( )
188
+ . ok_or_else( || #parse_object_error:: FieldNotStr { field: "kind" . to_owned( ) } ) ?;
189
+
190
+ // Note(@sbernauer): The kind must be checked here, because it is
191
+ // possible for the wrong object to be deserialized.
192
+ // Checking here stops us assuming the kind is correct and
193
+ // accidentally updating upgrade/downgrade information in the
194
+ // status in a later step.
195
+ if object_kind != #enum_ident_string {
196
+ return Err ( #parse_object_error:: UnexpectedKind {
197
+ kind: object_kind. to_owned( ) ,
198
+ expected: #enum_ident_string. to_owned( ) ,
199
+ } ) ;
200
+ }
201
+
202
+ let api_version = object_value
183
203
. get( "apiVersion" )
184
- . ok_or_else( || #parse_object_error:: FieldNotPresent ) ?
204
+ . ok_or_else( || #parse_object_error:: FieldMissing { field : "apiVersion" . to_owned ( ) } ) ?
185
205
. as_str( )
186
- . ok_or_else( || #parse_object_error:: FieldNotStr ) ?;
206
+ . ok_or_else( || #parse_object_error:: FieldNotStr { field : "apiVersion" . to_owned ( ) } ) ?;
187
207
188
208
let object = match api_version {
189
- #( #api_versions | #variant_strings => {
190
- let object = #serde_json_path:: from_value( value )
209
+ #( #api_versions => {
210
+ let object = #serde_json_path:: from_value( object_value )
191
211
. map_err( |source| #parse_object_error:: Deserialize { source } ) ?;
192
212
193
213
Self :: #variant_idents( object)
@@ -218,6 +238,7 @@ impl Struct {
218
238
219
239
fn generate_kubernetes_version_enum (
220
240
& self ,
241
+ kubernetes_arguments : & KubernetesArguments ,
221
242
tokens : & KubernetesTokens ,
222
243
vis : & Visibility ,
223
244
is_nested : bool ,
@@ -228,30 +249,55 @@ impl Struct {
228
249
// module (in standalone mode).
229
250
let automatically_derived = is_nested. not ( ) . then ( || quote ! { #[ automatically_derived] } ) ;
230
251
252
+ let versioned_path = & * kubernetes_arguments. crates . versioned ;
253
+ let unknown_desired_api_version_error =
254
+ quote ! { #versioned_path:: UnknownDesiredApiVersionError } ;
255
+
231
256
// Get the per-version items to be able to iterate over them via quote
232
257
let variant_strings = & tokens. variant_strings ;
233
258
let variant_idents = & tokens. variant_idents ;
259
+ let api_versions = variant_strings
260
+ . iter ( )
261
+ . map ( |version| format ! ( "{group}/{version}" , group = & kubernetes_arguments. group) )
262
+ . collect :: < Vec < _ > > ( ) ;
234
263
235
264
quote ! {
236
265
#automatically_derived
266
+ #[ derive( Copy , Clone , Debug ) ]
237
267
#vis enum #enum_ident {
238
268
#( #variant_idents) , *
239
269
}
240
270
241
271
#automatically_derived
242
272
impl :: std:: fmt:: Display for #enum_ident {
243
273
fn fmt( & self , f: & mut :: std:: fmt:: Formatter <' _>) -> :: std:: result:: Result <( ) , :: std:: fmt:: Error > {
244
- f. write_str( self . as_str( ) )
274
+ // The version (without the Kubernetes group) is probably more human-readable
275
+ f. write_str( self . as_version_str( ) )
245
276
}
246
277
}
247
278
248
279
#automatically_derived
249
280
impl #enum_ident {
250
- pub fn as_str ( & self ) -> & str {
281
+ pub fn as_version_str ( & self ) -> & str {
251
282
match self {
252
283
#( #variant_idents => #variant_strings) , *
253
284
}
254
285
}
286
+
287
+ pub fn as_api_version_str( & self ) -> & str {
288
+ match self {
289
+ #( #variant_idents => #api_versions) , *
290
+ }
291
+ }
292
+
293
+ pub fn from_api_version( api_version: & str ) -> Result <Self , #unknown_desired_api_version_error> {
294
+ match api_version {
295
+ #( #api_versions => Ok ( Self :: #variant_idents) ) , * ,
296
+ _ => Err ( #unknown_desired_api_version_error {
297
+ api_version: api_version. to_owned( ) ,
298
+ } ) ,
299
+ }
300
+ }
255
301
}
256
302
}
257
303
}
@@ -315,6 +361,7 @@ impl Struct {
315
361
let kubernetes_arguments = self . common . options . kubernetes_arguments . as_ref ( ) ?;
316
362
317
363
let struct_ident = & self . common . idents . kubernetes ;
364
+ let version_enum_ident = & self . common . idents . kubernetes_version ;
318
365
319
366
let kube_client_path = & * kubernetes_arguments. crates . kube_client ;
320
367
let serde_json_path = & * kubernetes_arguments. crates . serde_json ;
@@ -324,7 +371,7 @@ impl Struct {
324
371
let convert_object_error = quote ! { #versioned_path:: ConvertObjectError } ;
325
372
326
373
// Generate conversion paths and the match arms for these paths
327
- let match_arms =
374
+ let conversion_match_arms =
328
375
self . generate_kubernetes_conversion_match_arms ( versions, kubernetes_arguments) ;
329
376
330
377
// TODO (@Techassi): Make this a feature, drop the option from the macro arguments
@@ -375,11 +422,8 @@ impl Struct {
375
422
}
376
423
} ;
377
424
378
- // Extract the desired api version
379
- let desired_api_version = request. desired_api_version. as_str( ) ;
380
-
381
425
// Convert all objects into the desired version
382
- let response = match Self :: convert_objects( request. objects, desired_api_version) {
426
+ let response = match Self :: convert_objects( request. objects, & request . desired_api_version) {
383
427
:: std:: result:: Result :: Ok ( converted_objects) => {
384
428
#successful_conversion_response_event
385
429
@@ -426,20 +470,31 @@ impl Struct {
426
470
)
427
471
-> :: std:: result:: Result <:: std:: vec:: Vec <#serde_json_path:: Value >, #convert_object_error>
428
472
{
473
+ let desired_api_version = #version_enum_ident:: from_api_version( desired_api_version)
474
+ . map_err( |source| #convert_object_error:: ParseDesiredApiVersion { source } ) ?;
475
+
429
476
let mut converted_objects = :: std:: vec:: Vec :: with_capacity( objects. len( ) ) ;
430
477
431
478
for object in objects {
432
479
// This clone is required because in the noop case we move the object into
433
480
// the converted objects vec.
434
- let current_object = Self :: from_json_value ( object. clone( ) )
481
+ let current_object = Self :: from_json_object ( object. clone( ) )
435
482
. map_err( |source| #convert_object_error:: Parse { source } ) ?;
436
483
437
484
match ( current_object, desired_api_version) {
438
- #( #match_arms, ) *
439
- // If no match arm matches, this is a noop. This is the case if the desired
440
- // version matches the current object api version.
485
+ #( #conversion_match_arms, ) *
486
+ // In case the desired version matches the current object api version, we
487
+ // don't need to do anything.
488
+ //
441
489
// NOTE (@Techassi): I'm curious if this will ever happen? In theory the K8s
442
490
// apiserver should never send such a conversion review.
491
+ //
492
+ // Note(@sbernauer): I would prefer to explicitly list the remaining no-op
493
+ // cases, so the compiler ensures we did not miss a conversion
494
+ // // let version_idents = versions.iter().map(|v| &v.idents.variant);
495
+ // #(
496
+ // (Self::#version_idents(_), #version_enum_ident::#version_idents)
497
+ // )|* => converted_objects.push(object)
443
498
_ => converted_objects. push( object) ,
444
499
}
445
500
}
@@ -454,8 +509,10 @@ impl Struct {
454
509
versions : & [ VersionDefinition ] ,
455
510
kubernetes_arguments : & KubernetesArguments ,
456
511
) -> Vec < TokenStream > {
512
+ let group = & kubernetes_arguments. group ;
457
513
let variant_data_ident = & self . common . idents . kubernetes_parameter ;
458
514
let struct_ident = & self . common . idents . kubernetes ;
515
+ let version_enum_ident = & self . common . idents . kubernetes_version ;
459
516
let spec_ident = & self . common . idents . original ;
460
517
461
518
let versioned_path = & * kubernetes_arguments. crates . versioned ;
@@ -470,7 +527,10 @@ impl Struct {
470
527
let current_object_version_string = & start. inner . to_string ( ) ;
471
528
472
529
let desired_object_version = path. last ( ) . expect ( "the path always contains at least one element" ) ;
473
- let desired_object_version_string = desired_object_version. inner . to_string ( ) ;
530
+ let desired_object_api_version_string = format ! (
531
+ "{group}/{desired_object_version}" ,
532
+ desired_object_version = desired_object_version. inner
533
+ ) ;
474
534
let desired_object_variant_ident = & desired_object_version. idents . variant ;
475
535
let desired_object_module_ident = & desired_object_version. idents . module ;
476
536
@@ -493,7 +553,7 @@ impl Struct {
493
553
494
554
let convert_object_trace = kubernetes_arguments. options . enable_tracing . is_present ( ) . then ( || quote ! {
495
555
:: tracing:: trace!(
496
- #DESIRED_API_VERSION_ATTRIBUTE = #desired_object_version_string ,
556
+ #DESIRED_API_VERSION_ATTRIBUTE = #desired_object_api_version_string ,
497
557
#API_VERSION_ATTRIBUTE = #current_object_version_string,
498
558
#STEPS_ATTRIBUTE = #steps,
499
559
#KIND_ATTRIBUTE = #kind,
@@ -507,7 +567,7 @@ impl Struct {
507
567
. then ( || quote ! { status: #variant_data_ident. status, } ) ;
508
568
509
569
quote ! {
510
- ( Self :: #current_object_version_ident( #variant_data_ident) , #desired_object_version_string ) => {
570
+ ( Self :: #current_object_version_ident( #variant_data_ident) , #version_enum_ident :: #desired_object_variant_ident ) => {
511
571
#( #conversions) *
512
572
513
573
let desired_object = Self :: #desired_object_variant_ident( #desired_object_module_ident:: #struct_ident {
0 commit comments