cue icon indicating copy to clipboard operation
cue copied to clipboard

encoding/openapi: Duplicate enumeration values generated

Open inksnw opened this issue 1 year ago • 2 comments

What version of CUE are you using (cue version)?

$ cue version
v0.11.0

Does this issue reproduce with the latest stable release?

yes

What did you do?

package main

import (
	"cuelang.org/go/cue/cuecontext"
	"cuelang.org/go/encoding/openapi"
	"fmt"
)

func main() {
	def := `	
	#parameter: {
		volumes?: [...{
			type: *"emptyDir" | "pvc" 
			if type == "emptyDir" {
				medium: *"" | "Memory"
			}
		}]
	}
`
	ctx := cuecontext.New()
	value := ctx.CompileString(def)
	b, err := openapi.Gen(value, &openapi.Config{ExpandReferences: true})
	if err != nil {
		panic(err)
	}
	fmt.Println(string(b))
}

If I set ExpandReferences to false, it works, but it will generate links like "$ref": "#/components/schemas/xxx"

What did you expect to see?

{
    "medium": {
        "type": "string",
        "enum": [
            "",
            "Memory",
        ],
        "default": ""
    }
}

What did you see instead?

{
    "medium": {
        "type": "string",
        "enum": [
            "",
            "Memory",
            "Memory"
        ],
        "default": ""
    }
}

inksnw avatar Nov 27 '24 10:11 inksnw

Here's a testscript reproducer of a slightly more minimal example:

exec go mod tidy
exec go run .
cmp stdout want-stdout

-- go.mod --
module test

require cuelang.org/go v0.11.0

-- main.go --
package main

import (
	"encoding/json"
	"bytes"
	"fmt"

	"cuelang.org/go/cue/cuecontext"
	"cuelang.org/go/encoding/openapi"
)

func main() {
	def := `
	#parameter: {
		if true {
			a: *"" | "x" | "y"
		}
	}
`
	ctx := cuecontext.New()
	value := ctx.CompileString(def)
	b, err := openapi.Gen(value, &openapi.Config{ExpandReferences: true})
	if err != nil {
		panic(err)
	}
	var buf bytes.Buffer
	json.Indent(&buf, b, "", "    ")
	fmt.Println(string(buf.Bytes()))
}
-- want-stdout --
{
    "openapi": "3.0.0",
    "info": {
        "title": "Generated by cue.",
        "version": "no version"
    },
    "paths": {},
    "components": {
        "schemas": {
            "parameter": {
                "type": "object",
                "required": [
                    "a"
                ],
                "properties": {
                    "a": {
                        "type": "string",
                        "enum": [
                            "",
                            "x",
                            "y",
                        ],
                        "default": ""
                    }
                }
            }
        }
    }
}

This fails with:

> exec go mod tidy
> exec go run .
[stdout]
{
    "openapi": "3.0.0",
    "info": {
        "title": "Generated by cue.",
        "version": "no version"
    },
    "paths": {},
    "components": {
        "schemas": {
            "parameter": {
                "type": "object",
                "required": [
                    "a"
                ],
                "properties": {
                    "a": {
                        "type": "string",
                        "enum": [
                            "",
                            "x",
                            "y",
                            "x",
                            "y"
                        ],
                        "default": ""
                    }
                }
            }
        }
    }
}
> cmp stdout want-stdout
diff stdout want-stdout
--- stdout
+++ want-stdout
@@ -19,8 +19,6 @@
                             "",
                             "x",
                             "y",
-                            "x",
-                            "y"
                         ],
                         "default": ""
                     }

FAIL: /tmp/z.txtar:3: stdout and want-stdout differ

It's clear that the if comprehension is causing the issue here.

@inksnw As a workaround until this gets fixed, would it be possible for you to change the schema so it doesn't use comprehensions to define the enum?

For example:

#parameter: {
	volumes?: [...{
		type: *"emptyDir" | "pvc"
		medium?: *"" | "Memory"
		if type == "emptyDir" {
			medium: _
		}
	}]
}

FWIW it's generally considered to be better for schemas to be "pure" (i.e. avoid defining any regular fields or defaults), so you might define the #parameter schema as just:

#parameter: {
	volumes?: [...{
		type!: "emptyDir" | "pvc"
		medium?: "" | "Memory"
	}]
}

and the defaults in a separate field, e.g.

parameter: #parameter & {
	volumes?: [...{
		type: *"emptyDir" | "pvc"
		if type == "emptyDir" {
		    medium: *"" | "Memory"
		}
	}]
}

rogpeppe avatar Nov 27 '24 14:11 rogpeppe

Thanks for your reply. I'll give the workaround a try.

inksnw avatar Nov 28 '24 01:11 inksnw