Skip to content

Commit c826d56

Browse files
authored
Fix map of maps (#363)
Signed-off-by: Clément Hamada <clementhamada@protonmail.com>
1 parent 4babba0 commit c826d56

File tree

1 file changed

+60
-35
lines changed

1 file changed

+60
-35
lines changed

src/analyzer.rs

Lines changed: 60 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
505496
fn 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

Comments
 (0)