mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 20:17:04 +00:00
Fix: expand dotted field paths in ItemBuilder.Set (#7302)
* fix: expand dotted field paths in ItemBuilder.Set Signed-off-by: Krishnan K M <krishnankaruvattu@gmail.com> * Fix: preserve dots in bracketed SetIf paths Signed-off-by: Krishnan K M <krishnankaruvattu@gmail.com> --------- Signed-off-by: Krishnan K M <krishnankaruvattu@gmail.com>
This commit is contained in:
@@ -2406,8 +2406,7 @@ func (g *CUEGenerator) arrayElementToCUEWithDepth(elem *ArrayElement, depth int)
|
||||
if setIf, ok := op.(*SetIfOp); ok {
|
||||
condStr := g.conditionToCUE(setIf.Cond())
|
||||
valStr := indentMultilineValue(g.valueToCUE(setIf.Value()), innerIndent+"\t")
|
||||
// Convert dot-separated path to CUE shorthand syntax: "a.b.c" -> "a: b: c"
|
||||
cuePath := strings.ReplaceAll(setIf.Path(), ".", ": ")
|
||||
cuePath := itemFieldLabel(setIf.Path())
|
||||
sb.WriteString(fmt.Sprintf("%sif %s {\n", innerIndent, condStr))
|
||||
sb.WriteString(fmt.Sprintf("%s\t%s: %s\n", innerIndent, cuePath, valStr))
|
||||
sb.WriteString(fmt.Sprintf("%s}\n", innerIndent))
|
||||
@@ -2487,7 +2486,7 @@ func (g *CUEGenerator) arrayBuilderToCUE(ab *ArrayBuilder, depth int) string {
|
||||
if setIf, ok := op.(*SetIfOp); ok {
|
||||
condStr := g.conditionToCUE(setIf.Cond())
|
||||
valStr := g.valueToCUE(setIf.Value())
|
||||
cuePath := strings.ReplaceAll(setIf.Path(), ".", ": ")
|
||||
cuePath := itemFieldLabel(setIf.Path())
|
||||
sb.WriteString(fmt.Sprintf("%sif %s {\n", extraIndent, condStr))
|
||||
sb.WriteString(fmt.Sprintf("%s\t%s: %s\n", extraIndent, cuePath, valStr))
|
||||
sb.WriteString(fmt.Sprintf("%s}\n", extraIndent))
|
||||
@@ -2516,6 +2515,16 @@ func (g *CUEGenerator) arrayBuilderToCUE(ab *ArrayBuilder, depth int) string {
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// itemFieldLabel converts a dot-separated field path into CUE nested-field
|
||||
// shorthand. Dots inside brackets are preserved as part of the key.
|
||||
func itemFieldLabel(field string) string {
|
||||
parts := splitPath(field)
|
||||
if len(parts) < 2 {
|
||||
return field
|
||||
}
|
||||
return strings.Join(parts, ": ")
|
||||
}
|
||||
|
||||
// writeItemBuilderOps writes the CUE for ItemBuilder operations.
|
||||
func (g *CUEGenerator) writeItemBuilderOps(sb *strings.Builder, ops []itemOp, depth int) {
|
||||
indent := strings.Repeat(g.indent, depth)
|
||||
@@ -2524,7 +2533,7 @@ func (g *CUEGenerator) writeItemBuilderOps(sb *strings.Builder, ops []itemOp, de
|
||||
switch o := op.(type) {
|
||||
case setOp:
|
||||
valStr := g.valueToCUE(o.value)
|
||||
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, o.field, valStr))
|
||||
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, itemFieldLabel(o.field), valStr))
|
||||
|
||||
case ifBlockOp:
|
||||
condStr := g.conditionToCUE(o.cond)
|
||||
@@ -2538,7 +2547,7 @@ func (g *CUEGenerator) writeItemBuilderOps(sb *strings.Builder, ops []itemOp, de
|
||||
|
||||
case setDefaultOp:
|
||||
defStr := g.valueToCUE(o.defValue)
|
||||
sb.WriteString(fmt.Sprintf("%s%s: *%s | %s\n", indent, o.field, defStr, o.typeName))
|
||||
sb.WriteString(fmt.Sprintf("%s%s: *%s | %s\n", indent, itemFieldLabel(o.field), defStr, o.typeName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2749,6 +2749,117 @@ var _ = Describe("CUEGenerator", func() {
|
||||
Expect(cue).To(ContainSubstring("name: e.name"))
|
||||
Expect(cue).To(ContainSubstring("value: e.value"))
|
||||
})
|
||||
|
||||
It("ItemBuilder.Set expands a dotted path into nested fields", func() {
|
||||
maxRead := defkit.Int("maxReadRequestUnits").Optional()
|
||||
indexes := defkit.Array("indexes").WithFields(
|
||||
defkit.String("name"),
|
||||
).Optional()
|
||||
|
||||
c := defkit.NewComponent("table").
|
||||
Workload("v1", "ConfigMap").
|
||||
Params(indexes, maxRead).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
arr := defkit.NewArray().ForEachWith(indexes,
|
||||
func(item *defkit.ItemBuilder) {
|
||||
v := item.Var()
|
||||
item.Set("name", v.Field("name"))
|
||||
item.Set("onDemandThroughput.maxReadRequestUnits", maxRead)
|
||||
})
|
||||
tpl.Output(defkit.NewResource("v1", "ConfigMap").
|
||||
Set("spec.indexes", arr))
|
||||
})
|
||||
|
||||
cue := c.ToCue()
|
||||
Expect(cue).To(ContainSubstring("onDemandThroughput: maxReadRequestUnits: parameter.maxReadRequestUnits"))
|
||||
// The unexpanded label is not valid CUE and must not reappear.
|
||||
Expect(cue).NotTo(ContainSubstring("onDemandThroughput.maxReadRequestUnits:"))
|
||||
// A single-segment field is unaffected.
|
||||
Expect(cue).To(ContainSubstring("name: v.name"))
|
||||
Expect(parseCUE(cue)).To(Succeed())
|
||||
})
|
||||
|
||||
It("ItemBuilder.Set expands a dotted path inside a conditional block", func() {
|
||||
maxRead := defkit.Int("maxReadRequestUnits").Optional()
|
||||
maxWrite := defkit.Int("maxWriteRequestUnits").Optional()
|
||||
indexes := defkit.Array("indexes").WithFields(
|
||||
defkit.String("name"),
|
||||
).Optional()
|
||||
|
||||
c := defkit.NewComponent("table").
|
||||
Workload("v1", "ConfigMap").
|
||||
Params(indexes, maxRead, maxWrite).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
arr := defkit.NewArray().ForEachWith(indexes,
|
||||
func(item *defkit.ItemBuilder) {
|
||||
item.If(maxRead.IsSet(), func() {
|
||||
item.Set("onDemandThroughput.maxReadRequestUnits", maxRead)
|
||||
})
|
||||
item.If(maxWrite.IsSet(), func() {
|
||||
item.Set("onDemandThroughput.maxWriteRequestUnits", maxWrite)
|
||||
})
|
||||
})
|
||||
tpl.Output(defkit.NewResource("v1", "ConfigMap").
|
||||
Set("spec.indexes", arr))
|
||||
})
|
||||
|
||||
cue := c.ToCue()
|
||||
// Sibling writes to the same parent unify into one struct.
|
||||
Expect(cue).To(ContainSubstring("onDemandThroughput: maxReadRequestUnits: parameter.maxReadRequestUnits"))
|
||||
Expect(cue).To(ContainSubstring("onDemandThroughput: maxWriteRequestUnits: parameter.maxWriteRequestUnits"))
|
||||
Expect(parseCUE(cue)).To(Succeed())
|
||||
})
|
||||
|
||||
It("ItemBuilder.Set does not split on a dot inside a bracketed key", func() {
|
||||
items := defkit.Array("items").WithFields(
|
||||
defkit.String("value"),
|
||||
).Optional()
|
||||
|
||||
c := defkit.NewComponent("annotated").
|
||||
Workload("v1", "ConfigMap").
|
||||
Params(items).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
arr := defkit.NewArray().ForEachWith(items,
|
||||
func(item *defkit.ItemBuilder) {
|
||||
v := item.Var()
|
||||
item.Set(`metadata.annotations["example.com/owner"]`, v.Field("value"))
|
||||
})
|
||||
tpl.Output(defkit.NewResource("v1", "ConfigMap").
|
||||
Set("spec.items", arr))
|
||||
})
|
||||
|
||||
cue := c.ToCue()
|
||||
// The dot inside the brackets is part of the key, not a separator.
|
||||
// Bracketed keys are not otherwise supported here; this only guards
|
||||
// against one being split apart.
|
||||
Expect(cue).To(ContainSubstring(`annotations["example.com/owner"]: v.value`))
|
||||
Expect(cue).NotTo(ContainSubstring(`example: com/owner`))
|
||||
})
|
||||
|
||||
It("ArrayElement.SetIf preserves dots inside bracketed keys", func() {
|
||||
enabled := defkit.Bool("enabled").Optional()
|
||||
items := defkit.Array("items").WithFields(
|
||||
defkit.String("value"),
|
||||
).Optional()
|
||||
|
||||
c := defkit.NewComponent("annotated").
|
||||
Workload("v1", "ConfigMap").
|
||||
Params(enabled, items).
|
||||
Template(func(tpl *defkit.Template) {
|
||||
const path = `labels["app.kubernetes.io/name"]`
|
||||
arr := defkit.NewArray().
|
||||
Item(defkit.NewArrayElement().
|
||||
SetIf(enabled.IsSet(), path, defkit.Lit("static"))).
|
||||
ForEach(items, defkit.NewArrayElement().
|
||||
SetIf(enabled.IsSet(), path, defkit.Reference("m.value")))
|
||||
tpl.Output(defkit.NewResource("v1", "ConfigMap").
|
||||
Set("spec.items", arr))
|
||||
})
|
||||
|
||||
cue := c.ToCue()
|
||||
Expect(strings.Count(cue, `labels["app.kubernetes.io/name"]:`)).To(Equal(2))
|
||||
Expect(cue).NotTo(ContainSubstring(`labels["app: kubernetes: io/name"]:`))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("From().Filter().Map().Dedupe() pipeline", func() {
|
||||
|
||||
@@ -19,6 +19,7 @@ package defkit_test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"cuelang.org/go/cue/parser"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
@@ -27,3 +28,11 @@ func TestDefkit(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Defkit Suite")
|
||||
}
|
||||
|
||||
// parseCUE parses generated CUE and returns any syntax error. Substring
|
||||
// assertions cannot tell a well-formed definition from one that only breaks
|
||||
// once a parser reaches it.
|
||||
func parseCUE(src string) error {
|
||||
_, err := parser.ParseFile("generated.cue", src)
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user