@@ -245,23 +245,7 @@ fn extract_container(
245245 for ( key, value) in props {
246246 let value_type = value. type_ . clone ( ) . unwrap_or_default ( ) ;
247247 let rust_type = match value_type. as_ref ( ) {
248- "object" => {
249- let mut dict_key = None ;
250- if let Some ( additional) = & value. additional_properties {
251- dict_key = resolve_additional_properties ( additional, stack, key) ?;
252- } else if value. properties . is_none ( )
253- && value. x_kubernetes_preserve_unknown_fields . unwrap_or ( false )
254- {
255- dict_key = Some ( "serde_json::Value" . into ( ) ) ;
256- }
257- if let Some ( dict) = dict_key {
258- format ! ( "{}<String, {}>" , cfg. map. name( ) , dict)
259- } else if !cfg. no_object_reference && is_object_ref ( value) {
260- "ObjectReference" . into ( )
261- } else {
262- format ! ( "{}{}" , stack, key. to_upper_camel_case( ) )
263- }
264- }
248+ "object" => extract_object_type ( value, stack, key, cfg) ?,
265249 "string" => {
266250 if let Some ( _en) = & value. enum_ {
267251 trace ! ( "got enum string: {}" , serde_json:: to_string( & schema) . unwrap( ) ) ;
@@ -347,6 +331,7 @@ fn resolve_additional_properties(
347331 additional : & JSONSchemaPropsOrBool ,
348332 stack : & str ,
349333 key : & str ,
334+ cfg : & Config ,
350335) -> Result < Option < String > , anyhow:: Error > {
351336 debug ! ( "got additional: {}" , serde_json:: to_string( & additional) ?) ;
352337 let JSONSchemaPropsOrBool :: Schema ( s) = additional else {
@@ -361,11 +346,8 @@ fn resolve_additional_properties(
361346 // We are not 100% sure the array and object subcases here are correct but they pass tests atm.
362347 // authoratative, but more detailed sources than crd validation docs below are welcome
363348 // https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation
364- "array" => Some ( array_recurse_for_type ( s, stack, key, 1 , & Config :: default ( ) ) ?. 0 ) ,
365- "object" => {
366- // cluster test with `failureDomains` uses this spec format
367- Some ( format ! ( "{}{}" , stack, key. to_upper_camel_case( ) ) )
368- }
349+ "array" => Some ( array_recurse_for_type ( s, stack, key, 1 , cfg) ?. 0 ) ,
350+ "object" => Some ( extract_object_type ( s, stack, key, cfg) ?) ,
369351 "" => {
370352 if s. x_kubernetes_int_or_string . is_some ( ) {
371353 Some ( "IntOrString" . into ( ) )
@@ -402,19 +384,7 @@ fn array_recurse_for_type(
402384 let inner_array_type = s. type_ . clone ( ) . unwrap_or_default ( ) ;
403385 match inner_array_type. as_ref ( ) {
404386 "object" => {
405- // Same logic as in `extract_container` to simplify types to maps.
406- let mut dict_value = None ;
407- if let Some ( additional) = & s. additional_properties {
408- dict_value = resolve_additional_properties ( additional, stack, key) ?;
409- }
410-
411- let vec_value = if let Some ( dict_value) = dict_value {
412- let map_type = cfg. map . name ( ) ;
413- format ! ( "{map_type}<String, {dict_value}>" )
414- } else {
415- let structsuffix = key. to_upper_camel_case ( ) ;
416- format ! ( "{stack}{structsuffix}" )
417- } ;
387+ let vec_value = extract_object_type ( s, stack, key, cfg) ?;
418388
419389 Ok ( ( format ! ( "Vec<{}>" , vec_value) , level) )
420390 }
@@ -502,6 +472,27 @@ fn is_object_ref(value: &JSONSchemaProps) -> bool {
502472 false
503473}
504474
475+ fn extract_object_type (
476+ value : & JSONSchemaProps ,
477+ stack : & str ,
478+ key : & str ,
479+ cfg : & Config ,
480+ ) -> Result < String , anyhow:: Error > {
481+ let mut dict_key = None ;
482+ if let Some ( additional) = & value. additional_properties {
483+ dict_key = resolve_additional_properties ( additional, stack, key, cfg) ?;
484+ } else if value. properties . is_none ( ) && value. x_kubernetes_preserve_unknown_fields . unwrap_or ( false ) {
485+ dict_key = Some ( "serde_json::Value" . into ( ) ) ;
486+ }
487+ Ok ( if let Some ( dict) = dict_key {
488+ format ! ( "{}<String, {}>" , cfg. map. name( ) , dict)
489+ } else if !cfg. no_object_reference && is_object_ref ( value) {
490+ "ObjectReference" . into ( )
491+ } else {
492+ format ! ( "{}{}" , stack, key. to_upper_camel_case( ) )
493+ } )
494+ }
495+
505496fn extract_date_type ( value : & JSONSchemaProps ) -> Result < String > {
506497 Ok ( if let Some ( f) = & value. format {
507498 // NB: these need chrono feature on serde
@@ -624,6 +615,40 @@ mod test {
624615 assert_eq ! ( other. members[ 2 ] . type_, "String" ) ;
625616 }
626617
618+ #[ test]
619+ fn map_of_map ( ) {
620+ init ( ) ;
621+ // as found in cnpg-cluster
622+ let schema_str = r#"
623+ description: Instances topology.
624+ properties:
625+ instances:
626+ additionalProperties:
627+ additionalProperties:
628+ type: string
629+ description: PodTopologyLabels represent the topology of a Pod.
630+ map[labelName]labelValue
631+ type: object
632+ description: Instances contains the pod topology of the instances
633+ type: object
634+ type: object
635+ "# ;
636+ let schema: JSONSchemaProps = serde_yaml:: from_str ( schema_str) . unwrap ( ) ;
637+ //println!("schema: {}", serde_json::to_string_pretty(&schema).unwrap());
638+
639+ let structs = analyze ( schema, "ClusterStatusTopology" , Cfg :: default ( ) )
640+ . unwrap ( )
641+ . 0 ;
642+ //println!("{:?}", structs);
643+ let root = & structs[ 0 ] ;
644+ assert_eq ! ( root. name, "ClusterStatusTopology" ) ;
645+ assert_eq ! ( root. level, 0 ) ;
646+ // should have a member with a key to the map:
647+ let map = & root. members [ 0 ] ;
648+ assert_eq ! ( map. name, "instances" ) ;
649+ assert_eq ! ( map. type_, "Option<BTreeMap<String, BTreeMap<String, String>>>" ) ;
650+ }
651+
627652 #[ test]
628653 fn empty_preserve_unknown_fields ( ) {
629654 init ( ) ;
0 commit comments