Skip to content

Flattened untagged enums lead to invalid CRDs #1941

Description

@NickLarsenNZ

Current and expected behavior

Current bahaviour:

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

/// 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
    C {},
}

/// Put a [`UntaggedEnum`] behind `#[serde(flatten)]`
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
struct FlattenedUntaggedEnum {
    #[serde(flatten)]
    inner: UntaggedEnum,
}
Invalid CRD
{
  "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": "Used in case no fields are present",
                        "type": "object"
                      }
                    ],
                    "description": "Put a [`UntaggedEnum`] behind `#[serde(flatten)]`",
                    "properties": {
                      "one": {
                        "description": "Used in case the `one` field of type [`u32`] is present\nThis should not appear in the schema because the \"variant\" disappears\nand this comment cannot pertain to all fields within the struct variant.",
                        "type": "string"
                      },
                      "three": {
                        "type": "string"
                      },
                      "two": {
                        "description": "A very simple enum with unit variants",
                        "enum": [
                          "C",
                          "D",
                          "A",
                          "B"
                        ],
                        "type": "string"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "foo"
                ],
                "type": "object"
              }
            },
            "required": [
              "spec"
            ],
            "title": "FlattenedUntaggedEnumTest",
            "type": "object"
          }
        },
        "served": true,
        "storage": true,
        "subresources": {}
      }
    ]
  }
}

When applied to Kubernetes, the following errors are given:

The CustomResourceDefinition "flatteneduntaggedenumtests.clux.dev" is invalid: 
* spec.validation.openAPIV3Schema.properties[spec].properties[foo].anyOf[2].description: Forbidden: must be empty to be structural
* spec.validation.openAPIV3Schema.properties[spec].properties[foo].anyOf[2].type: Forbidden: must be empty to be structural

Expected behaviour

This test should pass

Notice the empty {} under the anyOf.

#[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": {}
                }
              ]
            }
          }
        )
    );
}

Which fails with:

json atom at path ".spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.foo.properties.one.description" is missing from rhs

json atom at path ".spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.foo.anyOf[2].type" is missing from rhs

json atom at path ".spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.foo.anyOf[2].description" is missing from rhs

Possible solution

This is another one that was fixed properly in #1839.
Specifically:

https://github.com/stackabletech/kube-rs/blob/71707abf1109bad01bbf2c46e3b161846049767a/kube-core/src/schema/transform_properties.rs#L89-L95

Additional context

No response

Environment

N/A

Configuration and features

No response

YAML

No response

Affected crates

No response

Would you like to work on fixing this bug?

yes

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions