Skip to content
Merged
79 changes: 70 additions & 9 deletions src/analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub struct Config {
pub fn analyze(schema: JSONSchemaProps, kind: &str, cfg: Config) -> Result<Output> {
let mut res = vec![];

analyze_(&schema, "", &kind.to_upper_camel_case(), 0, &mut res, &cfg)?;
analyze_(&schema, "", kind, 0, &mut res, &cfg)?;
Ok(Output(res))
}

Expand All @@ -45,6 +45,8 @@ fn analyze_(
let props = schema.properties.clone().unwrap_or_default();
let mut array_recurse_level: HashMap<String, u8> = Default::default();

let camel_cased_stack = &stack.to_upper_camel_case();

// create a Container if we have a container type:
//trace!("analyze_ with {} + {}", current, stack);
if schema.type_.clone().unwrap_or_default() == "object" {
Expand All @@ -55,29 +57,46 @@ fn analyze_(
// object with additionalProperties == map
if let Some(extra_props) = &s.properties {
// map values is an object with properties
debug!("Generating map struct for {} (under {})", current, stack);
let c = extract_container(extra_props, stack, &mut array_recurse_level, level, schema, cfg)?;
debug!(
"Generating map struct for {} (under {})",
current, camel_cased_stack
);
let c = extract_container(
extra_props,
camel_cased_stack,
&mut array_recurse_level,
level,
schema,
cfg,
)?;
results.push(c);
} else if dict_type == "object" {
// recurse to see if we eventually find properties
debug!(
"Recursing into nested additional properties for {} (under {})",
current, stack
current, camel_cased_stack
);
analyze_(s, current, stack, level, results, cfg)?;
analyze_(s, current, camel_cased_stack, level, results, cfg)?;
} else if !dict_type.is_empty() {
warn!("not generating type {} - using {} map", current, dict_type);
return Ok(()); // no members here - it'll be inlined
}
} else {
// else, regular properties only
debug!("Generating struct for {} (under {})", current, stack);
debug!("Generating struct for {} (under {})", current, camel_cased_stack);
// initial analysis of properties (we do not recurse here, we need to find members first)
if props.is_empty() && schema.x_kubernetes_preserve_unknown_fields.unwrap_or(false) {
warn!("not generating type {} - using map", current);
return Ok(());
}
let c = extract_container(&props, stack, &mut array_recurse_level, level, schema, cfg)?;
let c = extract_container(
&props,
camel_cased_stack,
&mut array_recurse_level,
level,
schema,
cfg,
)?;
results.push(c);
}
}
Expand All @@ -91,10 +110,24 @@ fn analyze_(
// again; additionalProperties XOR properties
let extras = if let Some(JSONSchemaPropsOrBool::Schema(s)) = schema.additional_properties.as_ref() {
let extra_props = s.properties.clone().unwrap_or_default();
find_containers(&extra_props, stack, &mut array_recurse_level, level, schema, cfg)?
find_containers(
&extra_props,
camel_cased_stack,
&mut array_recurse_level,
level,
schema,
cfg,
)?
} else {
// regular properties only
find_containers(&props, stack, &mut array_recurse_level, level, schema, cfg)?
find_containers(
&props,
camel_cased_stack,
&mut array_recurse_level,
level,
schema,
cfg,
)?
};
results.extend(extras);

Expand Down Expand Up @@ -1317,6 +1350,34 @@ type: object
assert_eq!(other.level, 1);
}

#[test]
fn camel_case_of_kinds_with_consecutive_upper_case_letters() {
init();
let schema_str = r#"
properties:
spec:
type: object
status:
type: object
type: object
"#;
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();

let structs = analyze(schema, "ArgoCDExport", Cfg::default()).unwrap().0;

let root = &structs[0];
assert_eq!(root.name, "ArgoCdExport");
assert_eq!(root.level, 0);

let spec = &structs[1];
assert_eq!(spec.name, "ArgoCdExportSpec");
assert_eq!(spec.level, 1);

let status = &structs[2];
assert_eq!(status.name, "ArgoCdExportStatus");
assert_eq!(status.level, 1);
}

#[test]
fn skipped_type_as_map_nested_in_array() {
init();
Expand Down
13 changes: 10 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::{path::PathBuf, str::FromStr};
#[macro_use] extern crate log;
use anyhow::{anyhow, Context, Result};
use clap::{CommandFactory, Parser, Subcommand};
use heck::ToUpperCamelCase;
use k8s_openapi::apiextensions_apiserver::pkg::apis::apiextensions::v1::{
CustomResourceDefinition, CustomResourceDefinitionVersion,
};
Expand Down Expand Up @@ -281,7 +282,7 @@ impl Kopium {
.is_some_and(|c| c.contains_key("status")))
&& self.has_status_resource(&structs)
{
println!(r#"#[kube(status = "{}Status")]"#, kind);
println!(r#"#[kube(status = "{}Status")]"#, kind.to_upper_camel_case());
}
if self.schema != "derived" {
println!(r#"#[kube(schema = "{}")]"#, self.schema);
Expand All @@ -306,7 +307,10 @@ impl Kopium {
}
} else {
self.print_derives(s, &structs);
let spec_trimmed_name = s.name.as_str().replace(&format!("{}Spec", kind), kind);
let spec_trimmed_name = s.name.as_str().replace(
&format!("{}Spec", kind.to_upper_camel_case()),
&kind.to_upper_camel_case(),
);
if s.is_enum {
println!("pub enum {} {{", spec_trimmed_name);
} else {
Expand All @@ -322,7 +326,10 @@ impl Kopium {
for annot in &m.extra_annot {
println!(" {}", annot);
}
let spec_trimmed_type = m.type_.as_str().replace(&format!("{}Spec", kind), kind);
let spec_trimmed_type = m.type_.as_str().replace(
&format!("{}Spec", kind.to_upper_camel_case()),
&kind.to_upper_camel_case(),
);
if s.is_enum {
// NB: only supporting plain enumerations atm, not oneOf
println!(" {},", name);
Expand Down
2 changes: 1 addition & 1 deletion src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ impl Container {
}

impl Output {
/// Rename all structs and all all their members to rust conventions
/// Rename all structs and all their members to rust conventions
///
/// Converts [*].members[*].name to snake_case for structs, PascalCase for enums,
/// and adds a serde(rename = "orig_name") annotation to `serde_annot`.
Expand Down
Loading
Loading