Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions kube-core/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ impl Transform for StructuralSchemaRewriter {
if let Some(subschemas) = &mut schema.subschemas {
if let Some(one_of) = subschemas.one_of.as_mut() {
// Tagged enums are serialized using `one_of`
hoist_subschema_properties(one_of, &mut schema.object, &mut schema.instance_type);
hoist_subschema_properties(one_of, &mut schema.object, &mut schema.instance_type, true);

// "Plain" enums are serialized using `one_of` if they have doc tags
hoist_subschema_enum_values(one_of, &mut schema.enum_values, &mut schema.instance_type);
Expand All @@ -340,7 +340,8 @@ impl Transform for StructuralSchemaRewriter {

if let Some(any_of) = &mut subschemas.any_of {
// Untagged enums are serialized using `any_of`
hoist_subschema_properties(any_of, &mut schema.object, &mut schema.instance_type);
// Variant descriptions are not pushed into properties (because they are not for the field).
hoist_subschema_properties(any_of, &mut schema.object, &mut schema.instance_type, false);
}
}

Expand Down Expand Up @@ -499,6 +500,7 @@ fn hoist_subschema_properties(
subschemas: &mut Vec<Schema>,
common_obj: &mut Option<Box<ObjectValidation>>,
instance_type: &mut Option<SingleOrVec<InstanceType>>,
push_description_to_property: bool,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this signature is getting a little awkard and is probably another refactoring candidate for refactoring later.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

) {
for variant in subschemas {
if let Schema::Object(SchemaObject {
Expand All @@ -518,7 +520,9 @@ fn hoist_subschema_properties(
let metadata = variant_object
.metadata
.get_or_insert_with(Box::<Metadata>::default);
metadata.description = Some(description);
if push_description_to_property {
metadata.description = Some(description);
}
}

// Move all properties
Expand Down Expand Up @@ -546,6 +550,20 @@ fn hoist_subschema_properties(

merge_metadata(instance_type, variant_type.take());
}
// Removes the type/description from oneOf and anyOf subschemas
else if let Schema::Object(SchemaObject {
metadata: variant_metadata,
instance_type: variant_type,
enum_values: None,
subschemas: None,
array: None,
object: None,
..
}) = variant
{
std::mem::take(&mut *variant_type);
std::mem::take(&mut *variant_metadata);
}
}
}

Expand Down
128 changes: 128 additions & 0 deletions kube-derive/tests/crd_complex_enum_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,34 @@ enum ComplexEnum {
},
}

/// An untagged enum with a nested enum inside
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[serde(untagged)]
enum UntaggedEnum {
/// Used in case the `one` field of type [`u32`] is present
///
/// This should not appear in the schema because the "variant" disappears
/// and this comment cannot pertain to all fields within the struct variant.
A { one: String },
/// Used in case the `two` field of type [`NormalEnum`] is present
///
/// This should not appear in the schema because the "variant" disappears
/// and this comment cannot pertain to all fields within the struct variant.
B { two: NormalEnum, three: String },
/// Used in case no fields are present
///
/// This should not appear in the schema because the "variant" disappears
/// and this comment cannot pertain to all fields within the struct variant.
C {},
Comment thread
doxxx93 marked this conversation as resolved.
}

/// Put a [`UntaggedEnum`] behind `#[serde(flatten)]`
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
struct FlattenedUntaggedEnum {
#[serde(flatten)]
inner: UntaggedEnum,
}

// CRD definitions

#[derive(CustomResource, Serialize, Deserialize, Debug, Clone, JsonSchema)]
Expand Down Expand Up @@ -77,9 +105,17 @@ struct ComplexEnumTestSpec {
#[kube(group = "clux.dev", version = "v1", kind = "OptionalComplexEnumTest")]
struct OptionalComplexEnumTestSpec {
/// Optional complex enum field
// When this doc-comment is missing, we suggest using the doc-comment of the
// inner type - though that is debatable
foo: Option<ComplexEnum>,
}

#[derive(CustomResource, Serialize, Deserialize, Debug, Clone, JsonSchema)]
#[kube(group = "clux.dev", version = "v1", kind = "FlattenedUntaggedEnumTest")]
struct FlattenedUntaggedEnumTestSpec {
foo: FlattenedUntaggedEnum,
}

#[test]
fn complex_enum() {
assert_json_eq!(
Expand Down Expand Up @@ -477,3 +513,95 @@ fn optional_complex_enum() {
})
);
}

#[test]
fn flattened_untagged_enum() {
assert_json_eq!(
FlattenedUntaggedEnumTest::crd(),
json!(
{
"apiVersion": "apiextensions.k8s.io/v1",
"kind": "CustomResourceDefinition",
"metadata": {
"name": "flatteneduntaggedenumtests.clux.dev"
},
"spec": {
"group": "clux.dev",
"names": {
"categories": [],
"kind": "FlattenedUntaggedEnumTest",
"plural": "flatteneduntaggedenumtests",
"shortNames": [],
"singular": "flatteneduntaggedenumtest"
},
"scope": "Cluster",
"versions": [
{
"additionalPrinterColumns": [],
"name": "v1",
"schema": {
"openAPIV3Schema": {
"description": "Auto-generated derived type for FlattenedUntaggedEnumTestSpec via `CustomResource`",
"properties": {
"spec": {
"properties": {
"foo": {
"anyOf": [
{
"required": [
"one"
]
},
{
"required": [
"three",
"two"
]
},
{}
],
"description": "Put a [`UntaggedEnum`] behind `#[serde(flatten)]`",
"properties": {
"one": {
"type": "string"
},
"two": {
"description": "A very simple enum with unit variants",
"enum": [
"C",
"D",
"A",
"B"
],
"type": "string"
},
"three": {
"type": "string"
},
},
"type": "object"
}
},
"required": [
"foo"
],
"type": "object"
}
},
"required": [
"spec"
],
"title": "FlattenedUntaggedEnumTest",
"type": "object"
}
},
"served": true,
"storage": true,
"subresources": {}
}
]
}
}
)
);
}