Skip to content

Conversation

@jmcarp
Copy link
Contributor

@jmcarp jmcarp commented Dec 2, 2025

In thinking through #342, I noticed that the enum type creation logic in createOneOf is a bit hard to follow, and not tested thoroughly. In advance of changing that behavior, I wanted to clean up the existing logic and add some tests. This should make the code more readable and easier to change in the future.

Relates to #350.
Relates to SSE-123.

@jmcarp jmcarp requested a review from a team as a code owner December 2, 2025 16:01
@sudomateo sudomateo linked an issue Dec 10, 2025 that may be closed by this pull request
@jmcarp jmcarp force-pushed the enum-cleanup branch 3 times, most recently from a3da7e4 to dc42f83 Compare December 10, 2025 23:12
Refactor the createOneOf function without changing the generated code:

* Use fewer loops over the oneOf portion of the schema.
* Use lists and maps to store state, rather than concatenated/split strings.
* Use clearer variable names throughout.
* More and clearer test cases.
@jmcarp
Copy link
Contributor Author

jmcarp commented Dec 11, 2025

Ready for review when someone needs a break! Assigning @lgfa29 since you've been reviewing the other refactors, but happy for any feedback.

Copy link
Member

@lgfa29 lgfa29 left a comment

Choose a reason for hiding this comment

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

What a tricky piece of code to write 😅

I think I get the gist of it, but the OpenAPI spec seems so broad in this area that there may be weird edge cases laying around that I didn't think about. None of them impact our current code generation for Nexus though, so we're good here.

Comment on lines +726 to +727
} else if len(propRef.Value.Enum) > 1 {
fmt.Printf("[WARN] TODO: oneOf for %q -> %q enum %#v\n", name, propName, propRef.Value.Enum)
Copy link
Member

Choose a reason for hiding this comment

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

I squinted at this for a while and I think there's a potential bug if a property has enum with more than one value, but I'm not sure if a spec like this would even make sense. I think, in general, you would create more variants?

The bug I saw by playing around with the tests is that convertToValidGoType uses propName for the struct name when enumField is empty, and so we end up with two structs with the same name (ImageSource in this case):

@@ -331,8 +332,9 @@ func Test_createOneOf(t *testing.T) {
                                                Value: &openapi3.Schema{
                                                        Type: &openapi3.Types{"object"},
                                                        Properties: map[string]*openapi3.SchemaRef{
-                                                               "type": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Enum: []any{"url"}}},
+                                                               "type": {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}, Enum: []any{"url", "abc"}}},
                                                                "url":  {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
+                                                               "abc":  {Value: &openapi3.Schema{Type: &openapi3.Types{"string"}}},
                                                        },
                                                        Required: []string{"type", "url"},
// ImageSourceType is the type definition for a ImageSourceType.
type ImageSourceType string
// ImageSource is the type definition for a ImageSource.
//
// Required fields:
// - Type
// - Url
type ImageSource struct {
        Abc string `json:"abc,omitempty" yaml:"abc,omitempty"`
        Type ImageSourceType `json:"type" yaml:"type"`
        Url string `json:"url" yaml:"url"`
}

// ImageSourceSnapshot is the type definition for a ImageSourceSnapshot.
//
// Required fields:
// - Id
// - Type
type ImageSourceSnapshot struct {
        Id string `json:"id" yaml:"id"`
        Type ImageSourceType `json:"type" yaml:"type"`
}

// ImageSource is the source of the underlying image.
type ImageSource struct {
        // Abc is the type definition for a Abc.
        Abc string `json:"abc,omitempty" yaml:"abc,omitempty"`
        // Type is the type definition for a Type.
        Type ImageSourceType `json:"type,omitempty" yaml:"type,omitempty"`
        // Url is the type definition for a Url.
        Url string `json:"url,omitempty" yaml:"url,omitempty"`
        // Id is the type definition for a Id.
        Id string `json:"id,omitempty" yaml:"id,omitempty"`
}

// ImageSourceTypeUrl represents the ImageSourceType `"url"`.
const ImageSourceTypeUrl ImageSourceType = "url"

// ImageSourceTypeAbc represents the ImageSourceType `"abc"`.
const ImageSourceTypeAbc ImageSourceType = "abc"

// ImageSourceTypeSnapshot represents the ImageSourceType `"snapshot"`.
const ImageSourceTypeSnapshot ImageSourceType = "snapshot"

But again, I think this is an unrealistic scenario, so probably safe to ignore.

The other thing I noticed is that this path is currently triggered once:

[WARN] TODO: oneOf for "DiskSource" -> "block_size" enum []interface {}{512, 2048, 4096}

But that sounds like a false positive? The generated code looks correct. This is just a bit of noise, so I think safe to ignore if it's hard to avoid it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I was also looking at duplicate struct generation when a pathological OpenAPI specification is created with oneOf variants. I updated the AddressLotKind scheme like so.

{
  "description": "The kind associated with an address lot.",
  "oneOf": [
    {
      "description": "Infrastructure address lots are used for network infrastructure like addresses assigned to rack switches.",
      "type": "string",
      "enum": [
        "infra"
      ]
    },
    {
      "description": "Pool address lots are used by IP pools.",
      "type": "string",
      "enum": [
        "pool"
      ]
    },
    {
      "description": "Testing things.",
      "type": "object",
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "foo"
          ]
        },
        "foo": {
          "type": "string"
        }
      },
      "required": [
        "allow",
        "type"
      ]
    }
  ]
}

Both the changes on this pull request and the current main changes generated the following duplicate types.

type AddressLotKind string
type AddressLotKindFoo struct {
    // ...
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

The other thing I noticed is that this path is currently triggered once:

[WARN] TODO: oneOf for "DiskSource" -> "block_size" enum []interface {}{512, 2048, 4096}

But that sounds like a false positive? The generated code looks correct. This is just a bit of noise, so I think safe to ignore if it's hard to avoid it.

This was an interesting one since it only occurs for this variant of DiskSource even though another variant uses block_size as well.

          {
            "description": "Create a blank disk that will accept bulk writes or pull blocks from an external source.",
            "type": "object",
            "properties": {
              "block_size": {
                "$ref": "#/components/schemas/BlockSize"
              },
              "type": {
                "type": "string",
                "enum": [
                  "importing_blocks"
                ]
              }
            },
            "required": [
              "block_size",
              "type"
            ]
          }

It's because the block_size here directly references the BlockSize schema whereas the other variant uses block_size behind an allOf, like so.

              "block_size": {
                "description": "size of blocks for this Disk. valid values are: 512, 2048, or 4096",
                "allOf": [
                  {
                    "$ref": "#/components/schemas/BlockSize"
                  }
                ]
              },

@jmcarp
Copy link
Contributor Author

jmcarp commented Dec 12, 2025

I'm going to merge this because I believe the edge cases discussed above (1) don't happen in nexus today and (2) are already present in main. But as we discussed earlier today, there's a lot we can improve here in future patches.

@jmcarp jmcarp merged commit 71fff1f into main Dec 12, 2025
1 check passed
@jmcarp jmcarp deleted the enum-cleanup branch December 12, 2025 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants