cue-lang / cue

The home of the CUE language! Validate and define text-based and dynamic configuration
https://cuelang.org
Apache License 2.0
5.16k stars 297 forks source link

encoding/openapi: Duplicate enumeration values generated #3596

Open inksnw opened 5 days ago

inksnw commented 5 days ago

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": ""
    }
}
rogpeppe commented 5 days ago

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"
        }
    }]
}
inksnw commented 4 days ago

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