@@ -129,6 +129,7 @@ impl Struct {
129129 let variant_data_ident = & self . common . idents . kubernetes_parameter ;
130130 let version_enum_ident = & self . common . idents . kubernetes_version ;
131131 let enum_ident = & self . common . idents . kubernetes ;
132+ let enum_ident_string = enum_ident. to_string ( ) ;
132133
133134 // Only add the #[automatically_derived] attribute if this impl is used outside of a
134135 // module (in standalone mode).
@@ -153,7 +154,8 @@ impl Struct {
153154 // Generate additional Kubernetes code, this is split out to reduce the complexity in this
154155 // function.
155156 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) ;
157159 let convert_method = self . generate_kubernetes_conversion ( versions) ;
158160
159161 let parse_object_error = quote ! { #versioned_path:: ParseObjectError } ;
@@ -173,21 +175,39 @@ impl Struct {
173175 #k8s_openapi_path:: apiextensions_apiserver:: pkg:: apis:: apiextensions:: v1:: CustomResourceDefinition ,
174176 #kube_core_path:: crd:: MergeError >
175177 {
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 ( ) )
177179 }
178180
179181 #convert_method
180182
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
183203 . get( "apiVersion" )
184- . ok_or_else( || #parse_object_error:: FieldNotPresent ) ?
204+ . ok_or_else( || #parse_object_error:: FieldMissing { field : "apiVersion" . to_owned ( ) } ) ?
185205 . as_str( )
186- . ok_or_else( || #parse_object_error:: FieldNotStr ) ?;
206+ . ok_or_else( || #parse_object_error:: FieldNotStr { field : "apiVersion" . to_owned ( ) } ) ?;
187207
188208 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 )
191211 . map_err( |source| #parse_object_error:: Deserialize { source } ) ?;
192212
193213 Self :: #variant_idents( object)
@@ -218,6 +238,7 @@ impl Struct {
218238
219239 fn generate_kubernetes_version_enum (
220240 & self ,
241+ kubernetes_arguments : & KubernetesArguments ,
221242 tokens : & KubernetesTokens ,
222243 vis : & Visibility ,
223244 is_nested : bool ,
@@ -228,30 +249,55 @@ impl Struct {
228249 // module (in standalone mode).
229250 let automatically_derived = is_nested. not ( ) . then ( || quote ! { #[ automatically_derived] } ) ;
230251
252+ let versioned_path = & * kubernetes_arguments. crates . versioned ;
253+ let unknown_desired_api_version_error =
254+ quote ! { #versioned_path:: UnknownDesiredApiVersionError } ;
255+
231256 // Get the per-version items to be able to iterate over them via quote
232257 let variant_strings = & tokens. variant_strings ;
233258 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 < _ > > ( ) ;
234263
235264 quote ! {
236265 #automatically_derived
266+ #[ derive( Copy , Clone , Debug ) ]
237267 #vis enum #enum_ident {
238268 #( #variant_idents) , *
239269 }
240270
241271 #automatically_derived
242272 impl :: std:: fmt:: Display for #enum_ident {
243273 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( ) )
245276 }
246277 }
247278
248279 #automatically_derived
249280 impl #enum_ident {
250- pub fn as_str ( & self ) -> & str {
281+ pub fn as_version_str ( & self ) -> & str {
251282 match self {
252283 #( #variant_idents => #variant_strings) , *
253284 }
254285 }
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+ }
255301 }
256302 }
257303 }
@@ -315,6 +361,7 @@ impl Struct {
315361 let kubernetes_arguments = self . common . options . kubernetes_arguments . as_ref ( ) ?;
316362
317363 let struct_ident = & self . common . idents . kubernetes ;
364+ let version_enum_ident = & self . common . idents . kubernetes_version ;
318365
319366 let kube_client_path = & * kubernetes_arguments. crates . kube_client ;
320367 let serde_json_path = & * kubernetes_arguments. crates . serde_json ;
@@ -324,7 +371,7 @@ impl Struct {
324371 let convert_object_error = quote ! { #versioned_path:: ConvertObjectError } ;
325372
326373 // Generate conversion paths and the match arms for these paths
327- let match_arms =
374+ let conversion_match_arms =
328375 self . generate_kubernetes_conversion_match_arms ( versions, kubernetes_arguments) ;
329376
330377 // TODO (@Techassi): Make this a feature, drop the option from the macro arguments
@@ -375,11 +422,8 @@ impl Struct {
375422 }
376423 } ;
377424
378- // Extract the desired api version
379- let desired_api_version = request. desired_api_version. as_str( ) ;
380-
381425 // 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) {
383427 :: std:: result:: Result :: Ok ( converted_objects) => {
384428 #successful_conversion_response_event
385429
@@ -426,20 +470,31 @@ impl Struct {
426470 )
427471 -> :: std:: result:: Result <:: std:: vec:: Vec <#serde_json_path:: Value >, #convert_object_error>
428472 {
473+ let desired_api_version = #version_enum_ident:: from_api_version( desired_api_version)
474+ . map_err( |source| #convert_object_error:: ParseDesiredApiVersion { source } ) ?;
475+
429476 let mut converted_objects = :: std:: vec:: Vec :: with_capacity( objects. len( ) ) ;
430477
431478 for object in objects {
432479 // This clone is required because in the noop case we move the object into
433480 // the converted objects vec.
434- let current_object = Self :: from_json_value ( object. clone( ) )
481+ let current_object = Self :: from_json_object ( object. clone( ) )
435482 . map_err( |source| #convert_object_error:: Parse { source } ) ?;
436483
437484 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+ //
441489 // NOTE (@Techassi): I'm curious if this will ever happen? In theory the K8s
442490 // 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)
443498 _ => converted_objects. push( object) ,
444499 }
445500 }
@@ -454,8 +509,10 @@ impl Struct {
454509 versions : & [ VersionDefinition ] ,
455510 kubernetes_arguments : & KubernetesArguments ,
456511 ) -> Vec < TokenStream > {
512+ let group = & kubernetes_arguments. group ;
457513 let variant_data_ident = & self . common . idents . kubernetes_parameter ;
458514 let struct_ident = & self . common . idents . kubernetes ;
515+ let version_enum_ident = & self . common . idents . kubernetes_version ;
459516 let spec_ident = & self . common . idents . original ;
460517
461518 let versioned_path = & * kubernetes_arguments. crates . versioned ;
@@ -470,7 +527,10 @@ impl Struct {
470527 let current_object_version_string = & start. inner . to_string ( ) ;
471528
472529 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+ ) ;
474534 let desired_object_variant_ident = & desired_object_version. idents . variant ;
475535 let desired_object_module_ident = & desired_object_version. idents . module ;
476536
@@ -493,7 +553,7 @@ impl Struct {
493553
494554 let convert_object_trace = kubernetes_arguments. options . enable_tracing . is_present ( ) . then ( || quote ! {
495555 :: tracing:: trace!(
496- #DESIRED_API_VERSION_ATTRIBUTE = #desired_object_version_string ,
556+ #DESIRED_API_VERSION_ATTRIBUTE = #desired_object_api_version_string ,
497557 #API_VERSION_ATTRIBUTE = #current_object_version_string,
498558 #STEPS_ATTRIBUTE = #steps,
499559 #KIND_ATTRIBUTE = #kind,
@@ -507,7 +567,7 @@ impl Struct {
507567 . then ( || quote ! { status: #variant_data_ident. status, } ) ;
508568
509569 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 ) => {
511571 #( #conversions) *
512572
513573 let desired_object = Self :: #desired_object_variant_ident( #desired_object_module_ident:: #struct_ident {
0 commit comments