Skip to content

Commit a7a010b

Browse files
committed
getting closer on plain enums
Signed-off-by: clux <[email protected]>
1 parent 4507db3 commit a7a010b

File tree

3 files changed

+104
-33
lines changed

3 files changed

+104
-33
lines changed

src/analyzer.rs

Lines changed: 84 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,11 @@ pub fn analyze(
120120
}
121121
x => {
122122
if let Some(en) = value.enum_ {
123+
// plain enums do not need to recurse, can collect it here
123124
let new_result = analyze_enum_properties(&en, &next_stack, level, &schema)?;
124-
results.extend(new_result);
125-
// TODO: this should probably be done outside of the property recursion loop
125+
results.push(new_result);
126126
} else {
127-
debug!("not recursing into {} (not a container - {})", key, x)
127+
debug!("not recursing into {} ('{}' is not a container)", key, x)
128128
}
129129
}
130130
}
@@ -138,8 +138,7 @@ fn analyze_enum_properties(
138138
stack: &str,
139139
level: u8,
140140
schema: &JSONSchemaProps,
141-
) -> Result<Vec<OutputStruct>, anyhow::Error> {
142-
let mut results = vec![];
141+
) -> Result<OutputStruct, anyhow::Error> {
143142
let mut members = vec![];
144143
debug!("analyzing enum {}", serde_json::to_string(&schema).unwrap());
145144
for en in items {
@@ -159,14 +158,13 @@ fn analyze_enum_properties(
159158
docs: member_doc,
160159
})
161160
}
162-
results.push(OutputStruct {
161+
Ok(OutputStruct {
163162
name: stack.to_string(),
164163
members,
165164
level,
166165
docs: schema.description.clone(),
167166
is_enum: true,
168-
});
169-
Ok(results)
167+
})
170168
}
171169

172170

@@ -180,10 +178,11 @@ fn analyze_object_properties(
180178
) -> Result<Vec<OutputStruct>, anyhow::Error> {
181179
let mut results = vec![];
182180
let mut members = vec![];
183-
let mut is_enum = false;
184181
//debug!("analyzing object {}", serde_json::to_string(&schema).unwrap());
182+
debug!("analyze object props in {}", stack);
185183
let reqs = schema.required.clone().unwrap_or_default();
186184
for (key, value) in props {
185+
debug!("analyze key {}", key);
187186
let value_type = value.type_.clone().unwrap_or_default();
188187
let rust_type = match value_type.as_ref() {
189188
"object" => {
@@ -260,9 +259,8 @@ fn analyze_object_properties(
260259
}
261260
}
262261
"string" => {
263-
debug!("got string schema: {}", serde_json::to_string(&schema).unwrap());
264262
if let Some(_en) = &value.enum_ {
265-
is_enum = true;
263+
debug!("got enum string: {}", serde_json::to_string(&schema).unwrap());
266264
format!("{}{}", stack, uppercase_first_letter(key))
267265
} else {
268266
"String".to_string()
@@ -308,17 +306,21 @@ fn analyze_object_properties(
308306
members.push(OutputMember {
309307
type_: format!("Option<{}>", rust_type),
310308
name: key.to_string(),
311-
field_annot: Some(r#"#[serde(default, skip_serializing_if = "Option::is_none")]"#.into()),
309+
field_annot: Some(r#"#[serde(default , skip_serializing_if = "Option::is_none")]"#.into()),
312310
docs: member_doc,
313311
})
312+
// TODO: must capture `default` key here instead of blindly using serde default
313+
// this will require us storing default properties for the member in above loop
314+
// This is complicated because serde default requires a default fn / impl Default
315+
// probably better to do impl Default to avoid having to make custom fns
314316
}
315317
}
316318
results.push(OutputStruct {
317319
name: stack.to_string(),
318320
members,
319321
level,
320322
docs: schema.description.clone(),
321-
is_enum,
323+
is_enum: false,
322324
});
323325
Ok(results)
324326
}
@@ -569,7 +571,7 @@ type: object
569571
"#;
570572

571573
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
572-
env_logger::init();
574+
//env_logger::init();
573575
let mut structs = vec![];
574576
analyze(schema, "", "MatchExpressions", 0, &mut structs).unwrap();
575577
println!("got {:?}", structs);
@@ -596,7 +598,72 @@ type: object
596598
}
597599

598600
#[test]
599-
fn enum_nested() {
601+
fn enum_string_within_container() {
602+
let schema_str = r#"
603+
description: Endpoint
604+
properties:
605+
relabelings:
606+
items:
607+
properties:
608+
action:
609+
default: replace
610+
description: Action to perform based on regex matching.
611+
Default is 'replace'
612+
enum:
613+
- replace
614+
- keep
615+
- drop
616+
- hashmod
617+
- labelmap
618+
- labeldrop
619+
- labelkeep
620+
type: string
621+
modulus:
622+
format: int64
623+
type: integer
624+
type: object
625+
type: array
626+
type: object
627+
"#;
628+
629+
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
630+
//env_logger::init();
631+
let mut structs = vec![];
632+
analyze(schema, "", "Endpoint", 0, &mut structs).unwrap();
633+
println!("got {:?}", structs);
634+
let root = &structs[0];
635+
assert_eq!(root.name, "Endpoint");
636+
assert_eq!(root.level, 0);
637+
assert_eq!(root.is_enum, false);
638+
assert_eq!(&root.members[0].name, "relabelings");
639+
assert_eq!(&root.members[0].type_, "Option<Vec<EndpointRelabelings>>");
640+
641+
let rel = &structs[1];
642+
assert_eq!(rel.name, "EndpointRelabelings");
643+
assert_eq!(rel.is_enum, false);
644+
assert_eq!(&rel.members[0].name, "action");
645+
assert_eq!(&rel.members[0].type_, "Option<EndpointRelabelingsAction>");
646+
// TODO: verify rel.members[0].field_annot uses correct default
647+
648+
// action enum member
649+
let act = &structs[2];
650+
assert_eq!(act.name, "EndpointRelabelingsAction");
651+
assert_eq!(act.is_enum, true);
652+
653+
// should have enum members:
654+
assert_eq!(&act.members[0].name, "replace");
655+
assert_eq!(&act.members[0].type_, "");
656+
assert_eq!(&act.members[1].name, "keep");
657+
assert_eq!(&act.members[1].type_, "");
658+
assert_eq!(&act.members[2].name, "drop");
659+
assert_eq!(&act.members[2].type_, "");
660+
assert_eq!(&act.members[3].name, "hashmod");
661+
assert_eq!(&act.members[3].type_, "");
662+
}
663+
664+
#[test]
665+
#[ignore] // oneof support not done
666+
fn enum_oneof() {
600667
let schema_str = r#"
601668
description: "Auto-generated derived type for ServerSpec via `CustomResource`"
602669
properties:
@@ -645,7 +712,7 @@ type: object
645712
type: object"#;
646713

647714
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
648-
env_logger::init();
715+
//env_logger::init();
649716
let mut structs = vec![];
650717
analyze(schema, "", "ServerSpec", 0, &mut structs).unwrap();
651718
println!("got {:?}", structs);
@@ -718,7 +785,7 @@ type: object
718785
type: object
719786
"#;
720787
let schema: JSONSchemaProps = serde_yaml::from_str(schema_str).unwrap();
721-
env_logger::init();
788+
//env_logger::init();
722789
let mut structs = vec![];
723790
analyze(schema, "Endpoints", "ServiceMonitor", 0, &mut structs).unwrap();
724791
println!("got {:?}", structs);

src/main.rs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -271,15 +271,23 @@ impl Kopium {
271271
println!(r#"#[kube(schema = "{}")]"#, self.schema);
272272
}
273273
}
274-
println!("pub struct {} {{", s.name);
274+
if s.is_enum {
275+
println!("pub enum {} {{", s.name);
276+
} else {
277+
println!("pub struct {} {{", s.name);
278+
}
275279
} else {
276280
self.print_derives(false);
277281
let spec_trimmed_name = s.name.as_str().replace(&format!("{}Spec", kind), &kind);
278-
println!("pub struct {} {{", spec_trimmed_name);
282+
if s.is_enum {
283+
println!("pub enum {} {{", spec_trimmed_name);
284+
} else {
285+
println!("pub struct {} {{", spec_trimmed_name);
286+
}
279287
}
280288
for m in s.members {
281289
self.print_docstr(m.docs, " ");
282-
let name = if self.snake_case {
290+
let name = if self.snake_case && !s.is_enum {
283291
let converted = m.name.to_snake_case();
284292
if converted != m.name {
285293
println!(" #[serde(rename = \"{}\")]", m.name);
@@ -298,15 +306,20 @@ impl Kopium {
298306
};
299307
let spec_trimmed_type = m.type_.as_str().replace(&format!("{}Spec", kind), &kind);
300308
if self.builders {
301-
if spec_trimmed_type.starts_with("Option") {
309+
if spec_trimmed_type.starts_with("Option<") {
302310
println!("#[builder(default, setter(strip_option))]");
303-
} else if spec_trimmed_type.starts_with("Vec")
304-
|| spec_trimmed_type.starts_with("BTreeMap")
311+
} else if spec_trimmed_type.starts_with("Vec<")
312+
|| spec_trimmed_type.starts_with("BTreeMap<")
305313
{
306314
println!("#[builder(default)]");
307315
}
308316
}
309-
println!(" pub {}: {},", safe_name, spec_trimmed_type);
317+
if s.is_enum {
318+
// NB: only supporting plain enumerations atm, not oneOf
319+
println!(" {},", safe_name);
320+
} else {
321+
println!(" pub {}: {},", safe_name, spec_trimmed_type);
322+
}
310323
}
311324
println!("}}");
312325
println!();

tests/servicemon-crd.yaml

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
apiVersion: apiextensions.k8s.io/v1
33
kind: CustomResourceDefinition
44
metadata:
5-
annotations:
6-
controller-gen.kubebuilder.io/version: v0.8.0
7-
creationTimestamp: null
85
name: servicemonitors.monitoring.coreos.com
96
spec:
107
group: monitoring.coreos.com
@@ -637,9 +634,3 @@ spec:
637634
type: object
638635
served: true
639636
storage: true
640-
status:
641-
acceptedNames:
642-
kind: ""
643-
plural: ""
644-
conditions: []
645-
storedVersions: []

0 commit comments

Comments
 (0)