Skip to content
Draft
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
69 changes: 67 additions & 2 deletions internal/openapi/openapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ type Node struct {
Comment string
Parent *Node
Child map[string]*Node
Order int
}

func newNode(name string, p *Node) *Node {
Expand All @@ -196,6 +197,7 @@ func ensure(root *Node, name string) *Node {

func Build(rows []Raw) *Node {
root := newNode("Config", nil)
orderCounter := 0
isPrim := func(s string) bool { return isPrimitive(strings.TrimPrefix(s, "*")) }
addImplicit := func(name string) {
if name == "" || isPrim(name) || strings.HasPrefix(name, "[]") || strings.HasPrefix(name, "map[") {
Expand All @@ -222,6 +224,8 @@ func Build(rows []Raw) *Node {
if i == len(r.Path)-1 {
if r.K == kParam {
cur.IsParam = true
cur.Order = orderCounter
orderCounter++
}
cur.TypeExpr = r.TypeExpr
cur.Comment = r.Description
Expand Down Expand Up @@ -260,6 +264,11 @@ type gen struct {
def map[string]bool
}

// NewGen creates a new generator instance
func NewGen(pkg string) *gen {
return &gen{pkg: pkg}
}

func (g *gen) addImpAlias(path, alias string) {
if g.imp == nil {
g.imp = map[string]string{}
Expand Down Expand Up @@ -422,7 +431,7 @@ func (g *gen) writeStruct(n *Node) {
g.buf.WriteString("}\n\n")

g.buf.WriteString("type ConfigSpec struct {\n")
keys := sortedKeys(n.Child)
keys := sortedKeysByOrder(n.Child)
for _, k := range keys {
c := n.Child[k]
if !c.IsParam {
Expand All @@ -448,7 +457,7 @@ func (g *gen) writeStruct(n *Node) {
}

g.buf.WriteString(fmt.Sprintf("type %s struct {\n", name))
keys := sortedKeys(n.Child)
keys := sortedKeysByOrder(n.Child)
for _, k := range keys {
g.emitField(n.Child[k])
}
Expand Down Expand Up @@ -566,6 +575,22 @@ func sortedKeys[M ~map[K]V, K comparable, V any](m M) []K {
return keys
}

func sortedKeysByOrder(nodes map[string]*Node) []string {
keys := make([]string, 0, len(nodes))
for k := range nodes {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool {
nodeI := nodes[keys[i]]
nodeJ := nodes[keys[j]]
if nodeI.Order != 0 || nodeJ.Order != 0 {
return nodeI.Order < nodeJ.Order
}
return keys[i] < keys[j]
})
Comment on lines +583 to +590

Choose a reason for hiding this comment

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

critical

The sorting logic in sortedKeysByOrder is flawed and doesn't correctly order parameters.

  1. The first parameter is assigned Order = 0, which is the same as the default for non-parameter fields. When comparing this parameter with a non-parameter, the if nodeI.Order != 0 || nodeJ.Order != 0 condition is false, causing it to fall back to alphabetical sorting. This is incorrect.
  2. When comparing a parameter with Order > 0 against a non-parameter with Order = 0, the condition nodeI.Order < nodeJ.Order will be false, causing the non-parameter to be sorted first.

A more robust approach is to use the IsParam boolean field, which clearly distinguishes parameters from other fields. This ensures parameters are always sorted before non-parameters, and then by their specified order.

sort.Slice(keys, func(i, j int) bool {
			nodeI := nodes[keys[i]]
			nodeJ := nodes[keys[j]]
			if nodeI.IsParam && nodeJ.IsParam {
				return nodeI.Order < nodeJ.Order
			}
			if nodeI.IsParam {
				return true
			}
			if nodeJ.IsParam {
				return false
			}
			return keys[i] < keys[j]
		})

return keys
}

/* -------------------------------------------------------------------------- */
/* Temporary project scaffolding */
/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -744,6 +769,10 @@ func CG(pkgDir string) ([]byte, error) {
/* -------------------------------------------------------------------------- */

func WriteValuesSchema(crdBytes []byte, outPath string) error {
return WriteValuesSchemaWithOrder(crdBytes, outPath, nil)
}

func WriteValuesSchemaWithOrder(crdBytes []byte, outPath string, root *Node) error {
docs := bytes.Split(crdBytes, []byte("\n---"))
if len(docs) == 0 {
return fmt.Errorf("empty CRD data")
Expand All @@ -758,6 +787,42 @@ func WriteValuesSchema(crdBytes []byte, outPath string) error {
}

specSchema := obj.Spec.Versions[0].Schema.OpenAPIV3Schema.Properties["spec"]

if root != nil {
keys := sortedKeysByOrder(root.Child)

var buf bytes.Buffer
buf.WriteString("{\n")
buf.WriteString(" \"title\": \"Chart Values\",\n")
buf.WriteString(" \"type\": \"object\",\n")
buf.WriteString(" \"properties\": {\n")

first := true
for _, key := range keys {
if node, exists := root.Child[key]; exists && node.IsParam {
if prop, exists := specSchema.Properties[key]; exists {
if !first {
buf.WriteString(",\n")
}
first = false

propJSON, err := json.MarshalIndent(prop, " ", " ")
if err != nil {
return err
}

buf.WriteString(fmt.Sprintf(" \"%s\": %s", key, string(propJSON)))
Comment on lines +809 to +814

Choose a reason for hiding this comment

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

medium

The manual JSON generation for properties results in incorrect indentation in the output values.schema.json file. Using json.MarshalIndent with a hardcoded prefix for the property value causes misalignment because the prefix is applied to every line of the marshaled object, including the first one.

To produce a correctly formatted JSON, you can marshal the property without a prefix and then manually handle the indentation to align it properly under its key.

propJSON, err := json.MarshalIndent(prop, "", "  ")
						if err != nil {
							return err
						}

						// Manually indent the property JSON to align correctly.
						lines := strings.Split(string(propJSON), "\n")
						buf.WriteString(fmt.Sprintf("    \"%s\": %s", key, lines[0]))
						for _, line := range lines[1:] {
							buf.WriteString("\n    " + line)
						}

}
}
}

buf.WriteString("\n }\n")
buf.WriteString("}\n")

return os.WriteFile(outPath, buf.Bytes(), 0o644)
}

// Fallback
out := struct {
Title string `json:"title"`
Type string `json:"type"`
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func main() {
}

if outSchema != "" {
if err := openapi.WriteValuesSchema(crdBytes, outSchema); err != nil {
if err := openapi.WriteValuesSchemaWithOrder(crdBytes, outSchema, tree); err != nil {
fmt.Printf("values schema: %v\n", err)
os.Exit(1)
}
Expand Down