mirror of
https://github.com/kubevela/kubevela.git
synced 2026-02-14 10:00:06 +00:00
Feat: Implement array builder and enhance CUE generation for X-Definitions (#7039)
* feat: implement array builder and enhance CUE generation with conditional outputs Signed-off-by: Amit Singh <singhamitch@outlook.com> Co-authored-by: Amit Singh <singhamitch@outlook.com> Co-authored-by: Amit Singh <singhamitch@outlook.com> Signed-off-by: Amit Singh <singhamitch@outlook.com> * feat: enhance CUE generation for struct fields and add support for array element types Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> Co-authored-by: Amit Singh <singhamitch@outlook.com> Signed-off-by: Amit Singh <singhamitch@outlook.com> * feat: refactor CUE generation logic for struct fields and streamline resource output handling Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> --------- Signed-off-by: Amit Singh <singhamitch@outlook.com> Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> Co-authored-by: Amit Singh <singhamitch@outlook.com>
This commit is contained in:
283
pkg/definition/defkit/array_builder.go
Normal file
283
pkg/definition/defkit/array_builder.go
Normal file
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
Copyright 2025 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package defkit
|
||||
|
||||
// entryKind describes what kind of array entry this is.
|
||||
type entryKind int
|
||||
|
||||
const (
|
||||
entryStatic entryKind = iota // always-present item
|
||||
entryConditional // conditional item (if cond { item })
|
||||
entryForEach // iterated item (for v in source { item })
|
||||
)
|
||||
|
||||
// arrayEntry is a single entry in an ArrayBuilder.
|
||||
type arrayEntry struct {
|
||||
kind entryKind
|
||||
element *ArrayElement // the item fields (for static, conditional, forEach)
|
||||
cond Condition // for conditional entries
|
||||
source Value // for forEach entries (iteration source)
|
||||
guard Condition // for forEach entries (optional guard: if source != _|_)
|
||||
itemBuilder *ItemBuilder // for forEachWith entries (complex per-item logic)
|
||||
}
|
||||
|
||||
// ArrayBuilder builds CUE arrays with static items, conditional items, and for-each items.
|
||||
// This is the core type for building arrays where some items are always present,
|
||||
// some are conditional, and some come from iteration.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// NewArray().
|
||||
// Item(cpuMetric).
|
||||
// ItemIf(mem.IsSet(), memMetric).
|
||||
// ForEachGuarded(podCustomMetrics.IsSet(), podCustomMetrics, customMetric)
|
||||
type ArrayBuilder struct {
|
||||
entries []arrayEntry
|
||||
}
|
||||
|
||||
func (a *ArrayBuilder) value() {}
|
||||
func (a *ArrayBuilder) expr() {}
|
||||
|
||||
// NewArray creates a new ArrayBuilder.
|
||||
func NewArray() *ArrayBuilder {
|
||||
return &ArrayBuilder{
|
||||
entries: make([]arrayEntry, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Item adds an always-present item to the array.
|
||||
func (a *ArrayBuilder) Item(elem *ArrayElement) *ArrayBuilder {
|
||||
a.entries = append(a.entries, arrayEntry{
|
||||
kind: entryStatic,
|
||||
element: elem,
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// ItemIf adds a conditional item to the array.
|
||||
// The item is only included when the condition is true.
|
||||
func (a *ArrayBuilder) ItemIf(cond Condition, elem *ArrayElement) *ArrayBuilder {
|
||||
a.entries = append(a.entries, arrayEntry{
|
||||
kind: entryConditional,
|
||||
element: elem,
|
||||
cond: cond,
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// ForEach adds an iterated item to the array.
|
||||
// For each element in source, an item is created using the element template.
|
||||
// In the element template, use Reference("m.field") to reference iteration variable fields.
|
||||
func (a *ArrayBuilder) ForEach(source Value, elem *ArrayElement) *ArrayBuilder {
|
||||
a.entries = append(a.entries, arrayEntry{
|
||||
kind: entryForEach,
|
||||
element: elem,
|
||||
source: source,
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// ForEachGuarded adds a guarded iterated item to the array.
|
||||
// The guard condition (typically source.IsSet()) wraps the for loop.
|
||||
// Generates: if guard for m in source { item }
|
||||
func (a *ArrayBuilder) ForEachGuarded(guard Condition, source Value, elem *ArrayElement) *ArrayBuilder {
|
||||
a.entries = append(a.entries, arrayEntry{
|
||||
kind: entryForEach,
|
||||
element: elem,
|
||||
source: source,
|
||||
guard: guard,
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// Entries returns all entries in the array builder.
|
||||
func (a *ArrayBuilder) Entries() []arrayEntry { return a.entries }
|
||||
|
||||
// entryForEachWith indicates a complex iterated item using an ItemBuilder.
|
||||
const entryForEachWith entryKind = 3
|
||||
|
||||
// ForEachWith adds a complex iterated item to the array, using an ItemBuilder
|
||||
// callback for per-item operations like conditionals, let bindings, and defaults.
|
||||
// Uses "v" as the default iteration variable name.
|
||||
func (a *ArrayBuilder) ForEachWith(source Value, fn func(item *ItemBuilder)) *ArrayBuilder {
|
||||
return a.ForEachWithVar("v", source, fn)
|
||||
}
|
||||
|
||||
// ForEachWithVar is like ForEachWith but allows specifying the iteration variable name.
|
||||
func (a *ArrayBuilder) ForEachWithVar(varName string, source Value, fn func(item *ItemBuilder)) *ArrayBuilder {
|
||||
ib := &ItemBuilder{varName: varName, ops: make([]itemOp, 0)}
|
||||
fn(ib)
|
||||
a.entries = append(a.entries, arrayEntry{
|
||||
kind: entryForEachWith,
|
||||
source: source,
|
||||
itemBuilder: ib,
|
||||
})
|
||||
return a
|
||||
}
|
||||
|
||||
// --- ItemBuilder ---
|
||||
|
||||
// itemOp is a single operation recorded by the ItemBuilder.
|
||||
type itemOp interface {
|
||||
isItemOp()
|
||||
}
|
||||
|
||||
// setOp records an unconditional field assignment.
|
||||
type setOp struct {
|
||||
field string
|
||||
value Value
|
||||
}
|
||||
|
||||
func (setOp) isItemOp() {}
|
||||
|
||||
// ifBlockOp records a conditional block of nested operations.
|
||||
type ifBlockOp struct {
|
||||
cond Condition
|
||||
body []itemOp
|
||||
}
|
||||
|
||||
func (ifBlockOp) isItemOp() {}
|
||||
|
||||
// letOp records a private field binding (_name: value).
|
||||
type letOp struct {
|
||||
name string
|
||||
value Value
|
||||
}
|
||||
|
||||
func (letOp) isItemOp() {}
|
||||
|
||||
// setDefaultOp records a CUE default value: field: *defValue | typeName.
|
||||
type setDefaultOp struct {
|
||||
field string
|
||||
defValue Value
|
||||
typeName string
|
||||
}
|
||||
|
||||
func (setDefaultOp) isItemOp() {}
|
||||
|
||||
// ItemBuilder records per-item operations for complex ForEach iterations.
|
||||
// It supports field assignment, conditionals, let bindings, and CUE default values.
|
||||
type ItemBuilder struct {
|
||||
varName string
|
||||
ops []itemOp
|
||||
}
|
||||
|
||||
// Var returns a reference builder for the iteration variable.
|
||||
// Use v.Field("port") to reference v.port in CUE.
|
||||
func (b *ItemBuilder) Var() *IterVarBuilder {
|
||||
return &IterVarBuilder{varName: b.varName}
|
||||
}
|
||||
|
||||
// Set records an unconditional field assignment.
|
||||
func (b *ItemBuilder) Set(field string, value Value) {
|
||||
b.ops = append(b.ops, setOp{field: field, value: value})
|
||||
}
|
||||
|
||||
// If records a conditional block of operations.
|
||||
func (b *ItemBuilder) If(cond Condition, fn func()) {
|
||||
outer := b.ops
|
||||
b.ops = make([]itemOp, 0)
|
||||
fn()
|
||||
inner := b.ops
|
||||
b.ops = outer
|
||||
b.ops = append(b.ops, ifBlockOp{cond: cond, body: inner})
|
||||
}
|
||||
|
||||
// IfSet records a conditional block that executes when the iteration variable's field is set.
|
||||
// Generates CUE: if v.field != _|_ { ... }
|
||||
func (b *ItemBuilder) IfSet(field string, fn func()) {
|
||||
cond := &IterFieldExistsCondition{varName: b.varName, field: field}
|
||||
b.If(cond, fn)
|
||||
}
|
||||
|
||||
// IfNotSet records a conditional block that executes when the iteration variable's field is NOT set.
|
||||
// Generates CUE: if v.field == _|_ { ... }
|
||||
func (b *ItemBuilder) IfNotSet(field string, fn func()) {
|
||||
cond := &IterFieldExistsCondition{varName: b.varName, field: field, negate: true}
|
||||
b.If(cond, fn)
|
||||
}
|
||||
|
||||
// Let records a private field binding and returns a reference to it.
|
||||
// Generates CUE: _name: value
|
||||
func (b *ItemBuilder) Let(name string, value Value) Value {
|
||||
b.ops = append(b.ops, letOp{name: name, value: value})
|
||||
return &IterLetRef{name: name}
|
||||
}
|
||||
|
||||
// SetDefault records a CUE default value assignment.
|
||||
// Generates CUE: field: *defValue | typeName
|
||||
func (b *ItemBuilder) SetDefault(field string, defValue Value, typeName string) {
|
||||
b.ops = append(b.ops, setDefaultOp{field: field, defValue: defValue, typeName: typeName})
|
||||
}
|
||||
|
||||
// FieldExists returns a Condition that checks if the iteration variable's field is set.
|
||||
// Generates CUE: v.field != _|_
|
||||
func (b *ItemBuilder) FieldExists(field string) Condition {
|
||||
return &IterFieldExistsCondition{varName: b.varName, field: field}
|
||||
}
|
||||
|
||||
// FieldNotExists returns a Condition that checks if the iteration variable's field is NOT set.
|
||||
// Generates CUE: v.field == _|_
|
||||
func (b *ItemBuilder) FieldNotExists(field string) Condition {
|
||||
return &IterFieldExistsCondition{varName: b.varName, field: field, negate: true}
|
||||
}
|
||||
|
||||
// Ops returns the recorded operations.
|
||||
func (b *ItemBuilder) Ops() []itemOp { return b.ops }
|
||||
|
||||
// VarName returns the iteration variable name.
|
||||
func (b *ItemBuilder) VarName() string { return b.varName }
|
||||
|
||||
// IterVarBuilder provides access to iteration variable fields.
|
||||
type IterVarBuilder struct {
|
||||
varName string
|
||||
}
|
||||
|
||||
// Ref returns a Value referencing the iteration variable itself.
|
||||
// Generates CUE: v (or whatever the variable name is).
|
||||
// Useful when iterating over primitive arrays (e.g., [...int]).
|
||||
func (v *IterVarBuilder) Ref() *IterVarRef {
|
||||
return &IterVarRef{varName: v.varName}
|
||||
}
|
||||
|
||||
// Field returns a Value referencing the iteration variable's field.
|
||||
// v.Field("port") generates CUE: v.port
|
||||
func (v *IterVarBuilder) Field(name string) *IterFieldRef {
|
||||
return &IterFieldRef{varName: v.varName, field: name}
|
||||
}
|
||||
|
||||
// ArrayConcatValue represents an array concatenation: left + right.
|
||||
// Used for expressions like [items] + parameter.extraVolumeMounts.
|
||||
type ArrayConcatValue struct {
|
||||
left Value
|
||||
right Value
|
||||
}
|
||||
|
||||
func (a *ArrayConcatValue) value() {}
|
||||
func (a *ArrayConcatValue) expr() {}
|
||||
|
||||
// Left returns the left operand.
|
||||
func (a *ArrayConcatValue) Left() Value { return a.left }
|
||||
|
||||
// Right returns the right operand.
|
||||
func (a *ArrayConcatValue) Right() Value { return a.right }
|
||||
|
||||
// ArrayConcat creates an array concatenation expression.
|
||||
// Generates CUE: left + right
|
||||
func ArrayConcat(left, right Value) *ArrayConcatValue {
|
||||
return &ArrayConcatValue{left: left, right: right}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
type CollectionOp struct {
|
||||
source Value
|
||||
ops []collectionOperation
|
||||
guard Condition // optional guard: wraps entire comprehension in if cond
|
||||
}
|
||||
|
||||
type collectionOperation interface {
|
||||
@@ -60,6 +61,21 @@ func F(name string) FieldRef {
|
||||
func (c *CollectionOp) expr() {}
|
||||
func (c *CollectionOp) value() {}
|
||||
|
||||
// Guard adds a guard condition that wraps the entire comprehension.
|
||||
// When the guard is set, the generated CUE is:
|
||||
//
|
||||
// [if guard for v in source if filter {v}]
|
||||
//
|
||||
// This is used when the source may not exist and needs a
|
||||
// protective condition (e.g., if parameter.privileges != _|_).
|
||||
func (c *CollectionOp) Guard(cond Condition) *CollectionOp {
|
||||
c.guard = cond
|
||||
return c
|
||||
}
|
||||
|
||||
// GetGuard returns the guard condition, if any.
|
||||
func (c *CollectionOp) GetGuard() Condition { return c.guard }
|
||||
|
||||
// Filter keeps only items matching the predicate.
|
||||
// Usage: .Filter(Field("expose").Eq(true))
|
||||
func (c *CollectionOp) Filter(pred Predicate) *CollectionOp {
|
||||
|
||||
@@ -296,6 +296,9 @@ type Template struct {
|
||||
concatHelpers []*ConcatHelper // list.Concat helpers
|
||||
dedupeHelpers []*DedupeHelper // Deduplication helpers
|
||||
|
||||
// Output groups for grouped conditional outputs
|
||||
outputGroups []*outputGroup
|
||||
|
||||
// Trait-specific fields
|
||||
patch *PatchResource // Patch operations for traits
|
||||
patchStrategy string // Patch strategy (e.g., "retainKeys", "jsonMergePatch")
|
||||
@@ -416,6 +419,48 @@ func (t *Template) GetOutput() *Resource { return t.output }
|
||||
// GetOutputs returns all auxiliary resources.
|
||||
func (t *Template) GetOutputs() map[string]*Resource { return t.outputs }
|
||||
|
||||
// outputGroup represents a group of outputs that share a common condition.
|
||||
type outputGroup struct {
|
||||
cond Condition
|
||||
outputs map[string]*Resource
|
||||
}
|
||||
|
||||
// OutputGroup is a builder for adding outputs within a grouped condition.
|
||||
type OutputGroup struct {
|
||||
tpl *Template
|
||||
cond Condition
|
||||
outputs map[string]*Resource
|
||||
}
|
||||
|
||||
// Add adds a named resource to the output group.
|
||||
func (g *OutputGroup) Add(name string, r *Resource) *OutputGroup {
|
||||
g.outputs[name] = r
|
||||
return g
|
||||
}
|
||||
|
||||
// OutputsGroupIf groups multiple outputs under a single condition.
|
||||
// This generates one `if cond { ... }` block containing all grouped outputs.
|
||||
func (t *Template) OutputsGroupIf(cond Condition, fn func(g *OutputGroup)) {
|
||||
group := &OutputGroup{
|
||||
tpl: t,
|
||||
cond: cond,
|
||||
outputs: make(map[string]*Resource),
|
||||
}
|
||||
fn(group)
|
||||
if t.outputGroups == nil {
|
||||
t.outputGroups = make([]*outputGroup, 0)
|
||||
}
|
||||
t.outputGroups = append(t.outputGroups, &outputGroup{
|
||||
cond: cond,
|
||||
outputs: group.outputs,
|
||||
})
|
||||
}
|
||||
|
||||
// GetOutputGroups returns all output groups.
|
||||
func (t *Template) GetOutputGroups() []*outputGroup {
|
||||
return t.outputGroups
|
||||
}
|
||||
|
||||
// --- Patch methods for traits ---
|
||||
|
||||
// Patch returns the PatchResource builder for traits.
|
||||
|
||||
@@ -21,6 +21,15 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// cueLabel quotes a CUE field label if it contains characters that are not
|
||||
// valid in a CUE identifier (letters, digits, underscore, $).
|
||||
func cueLabel(name string) string {
|
||||
if strings.ContainsAny(name, "-./") {
|
||||
return fmt.Sprintf("%q", name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
// CUEGenerator generates CUE definitions from Go component definitions.
|
||||
type CUEGenerator struct {
|
||||
indent string
|
||||
@@ -247,12 +256,8 @@ func (g *CUEGenerator) GenerateFullDefinition(c *ComponentDefinition) string {
|
||||
sb.WriteString(")\n\n")
|
||||
}
|
||||
|
||||
// Write component header - quote names that contain special characters
|
||||
name := c.GetName()
|
||||
if strings.ContainsAny(name, "-./") {
|
||||
name = fmt.Sprintf("%q", name)
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s: {\n", name))
|
||||
// Write component header
|
||||
sb.WriteString(fmt.Sprintf("%s: {\n", cueLabel(c.GetName())))
|
||||
sb.WriteString(fmt.Sprintf("%stype: \"component\"\n", g.indent))
|
||||
sb.WriteString(fmt.Sprintf("%sannotations: {}\n", g.indent))
|
||||
sb.WriteString(fmt.Sprintf("%slabels: {}\n", g.indent))
|
||||
@@ -467,9 +472,31 @@ func (g *CUEGenerator) writeStructFieldForHelper(sb *strings.Builder, f *StructF
|
||||
// Get CUE type for the field type
|
||||
fieldType := g.cueTypeForParamType(f.FieldType())
|
||||
|
||||
if f.HasDefault() {
|
||||
sb.WriteString(fmt.Sprintf("%s%s: *%v | %s\n", indent, name, formatCUEValue(f.GetDefault()), fieldType))
|
||||
} else {
|
||||
switch {
|
||||
case f.HasDefault():
|
||||
enumValues := f.GetEnumValues()
|
||||
switch {
|
||||
case len(enumValues) > 0:
|
||||
// Enum with default: *"default" | "other1" | "other2"
|
||||
defaultStr := fmt.Sprintf("%v", f.GetDefault())
|
||||
var enumParts []string
|
||||
enumParts = append(enumParts, fmt.Sprintf("*%s", formatCUEValue(f.GetDefault())))
|
||||
for _, v := range enumValues {
|
||||
if v != defaultStr {
|
||||
enumParts = append(enumParts, fmt.Sprintf("%q", v))
|
||||
}
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, name, strings.Join(enumParts, " | ")))
|
||||
case f.FieldType() == ParamTypeArray && f.GetElementType() != "":
|
||||
elemCUE := g.cueTypeForParamType(f.GetElementType())
|
||||
sb.WriteString(fmt.Sprintf("%s%s: *%v | [...%s]\n", indent, name, formatCUEValue(f.GetDefault()), elemCUE))
|
||||
default:
|
||||
sb.WriteString(fmt.Sprintf("%s%s: *%v | %s\n", indent, name, formatCUEValue(f.GetDefault()), fieldType))
|
||||
}
|
||||
case f.FieldType() == ParamTypeArray && f.GetElementType() != "":
|
||||
elemCUE := g.cueTypeForParamType(f.GetElementType())
|
||||
sb.WriteString(fmt.Sprintf("%s%s%s: [...%s]\n", indent, name, optional, elemCUE))
|
||||
default:
|
||||
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, optional, fieldType))
|
||||
}
|
||||
}
|
||||
@@ -833,7 +860,8 @@ func (g *CUEGenerator) writeResourceOutput(sb *strings.Builder, name string, res
|
||||
indent = strings.Repeat(g.indent, depth)
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("%s%s: {\n", indent, name))
|
||||
quotedName := cueLabel(name)
|
||||
sb.WriteString(fmt.Sprintf("%s%s: {\n", indent, quotedName))
|
||||
innerIndent := strings.Repeat(g.indent, depth+1)
|
||||
|
||||
// Handle conditional apiVersion or static apiVersion
|
||||
@@ -1229,22 +1257,8 @@ func (g *CUEGenerator) valueToCUE(v Value) string {
|
||||
case *HelperVar:
|
||||
// Return reference to the helper by name
|
||||
return val.Name()
|
||||
case *StringParam:
|
||||
return "parameter." + val.Name()
|
||||
case *IntParam:
|
||||
return "parameter." + val.Name()
|
||||
case *BoolParam:
|
||||
return "parameter." + val.Name()
|
||||
case *FloatParam:
|
||||
return "parameter." + val.Name()
|
||||
case *ArrayParam:
|
||||
return "parameter." + val.Name()
|
||||
case *MapParam:
|
||||
return "parameter." + val.Name()
|
||||
case *StringKeyMapParam:
|
||||
return "parameter." + val.Name()
|
||||
case *EnumParam:
|
||||
return "parameter." + val.Name()
|
||||
case *StringParam, *IntParam, *BoolParam, *FloatParam, *ArrayParam, *MapParam, *StringKeyMapParam, *EnumParam:
|
||||
return "parameter." + v.(Param).Name()
|
||||
case *DynamicMapParam:
|
||||
// Dynamic map parameters reference just "parameter"
|
||||
return "parameter"
|
||||
@@ -1273,6 +1287,10 @@ func (g *CUEGenerator) valueToCUE(v Value) string {
|
||||
case *LetRef:
|
||||
// Return reference to a let binding variable
|
||||
return val.Name()
|
||||
case *ArrayBuilder:
|
||||
return g.arrayBuilderToCUE(val, 1)
|
||||
case *ArrayConcatValue:
|
||||
return g.valueToCUE(val.Left()) + " + " + g.valueToCUE(val.Right())
|
||||
case *ListComprehension:
|
||||
// Return list comprehension CUE
|
||||
return g.listComprehensionToCUE(val)
|
||||
@@ -1288,6 +1306,20 @@ func (g *CUEGenerator) valueToCUE(v Value) string {
|
||||
case *ParamFieldRef:
|
||||
// Reference to a field within a struct parameter: parameter.name.field.path
|
||||
return fmt.Sprintf("parameter.%s.%s", val.ParamName(), val.FieldPath())
|
||||
case *InterpolatedString:
|
||||
return g.interpolatedStringToCUE(val)
|
||||
case *PlusExpr:
|
||||
parts := make([]string, len(val.Parts()))
|
||||
for i, p := range val.Parts() {
|
||||
parts[i] = g.valueToCUE(p)
|
||||
}
|
||||
return strings.Join(parts, " + ")
|
||||
case *IterVarRef:
|
||||
return val.VarName()
|
||||
case *IterFieldRef:
|
||||
return fmt.Sprintf("%s.%s", val.VarName(), val.FieldName())
|
||||
case *IterLetRef:
|
||||
return val.RefName()
|
||||
default:
|
||||
// Try to get name from Param interface
|
||||
if p, ok := v.(Param); ok {
|
||||
@@ -1306,6 +1338,41 @@ func (g *CUEGenerator) cueFuncToCUE(fn *CUEFunc) string {
|
||||
return fmt.Sprintf("%s.%s(%s)", fn.Package(), fn.Function(), strings.Join(args, ", "))
|
||||
}
|
||||
|
||||
// interpolatedStringToCUE converts an InterpolatedString to CUE string interpolation.
|
||||
// Literal string values are inlined directly. All other values are wrapped in \(...).
|
||||
// Example: Interpolation(vela.Namespace(), Lit(":"), name) → "\(context.namespace):\(parameter.name)"
|
||||
func (g *CUEGenerator) interpolatedStringToCUE(is *InterpolatedString) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString(`"`)
|
||||
for _, part := range is.Parts() {
|
||||
if lit, ok := part.(*Literal); ok {
|
||||
if s, ok := lit.Val().(string); ok {
|
||||
sb.WriteString(s)
|
||||
continue
|
||||
}
|
||||
}
|
||||
sb.WriteString(`\(`)
|
||||
sb.WriteString(g.valueToCUE(part))
|
||||
sb.WriteString(`)`)
|
||||
}
|
||||
sb.WriteString(`"`)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// valueToCUEAtDepth converts a Value to CUE syntax with depth-aware indentation.
|
||||
// For types that support depth (ArrayBuilder, ArrayConcatValue), it uses the given depth.
|
||||
// For all other types, it falls back to the standard valueToCUE.
|
||||
func (g *CUEGenerator) valueToCUEAtDepth(v Value, depth int) string {
|
||||
switch val := v.(type) {
|
||||
case *ArrayBuilder:
|
||||
return g.arrayBuilderToCUE(val, depth)
|
||||
case *ArrayConcatValue:
|
||||
return g.valueToCUEAtDepth(val.Left(), depth) + " + " + g.valueToCUE(val.Right())
|
||||
default:
|
||||
return g.valueToCUE(v)
|
||||
}
|
||||
}
|
||||
|
||||
// arrayElementToCUE converts an ArrayElement to CUE syntax.
|
||||
// Uses default depth of 1 for backwards compatibility.
|
||||
func (g *CUEGenerator) arrayElementToCUE(elem *ArrayElement) string {
|
||||
@@ -1336,10 +1403,106 @@ func (g *CUEGenerator) arrayElementToCUEWithDepth(elem *ArrayElement, depth int)
|
||||
sb.WriteString(fmt.Sprintf("%s}\n", innerIndent))
|
||||
}
|
||||
}
|
||||
// Write patchKey-annotated fields (nested patchKey inside array elements)
|
||||
for _, pkf := range elem.PatchKeyFields() {
|
||||
sb.WriteString(fmt.Sprintf("%s// +patchKey=%s\n", innerIndent, pkf.key))
|
||||
valStr := g.valueToCUEAtDepth(pkf.value, depth+1)
|
||||
sb.WriteString(fmt.Sprintf("%s%s: %s\n", innerIndent, pkf.field, valStr))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s}", indent))
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// arrayBuilderToCUE converts an ArrayBuilder to CUE syntax.
|
||||
// Generates: [{static}, if cond {{conditional}}, if guard for m in source {iterated}]
|
||||
func (g *CUEGenerator) arrayBuilderToCUE(ab *ArrayBuilder, depth int) string {
|
||||
var sb strings.Builder
|
||||
indent := strings.Repeat(g.indent, depth)
|
||||
innerIndent := strings.Repeat(g.indent, depth+1)
|
||||
deepIndent := strings.Repeat(g.indent, depth+2)
|
||||
|
||||
sb.WriteString("[\n")
|
||||
|
||||
for _, entry := range ab.Entries() {
|
||||
switch entry.kind {
|
||||
case entryStatic:
|
||||
sb.WriteString(innerIndent)
|
||||
sb.WriteString(g.arrayElementToCUEWithDepth(entry.element, depth+1))
|
||||
sb.WriteString(",\n")
|
||||
|
||||
case entryConditional:
|
||||
condStr := g.conditionToCUE(entry.cond)
|
||||
sb.WriteString(fmt.Sprintf("%sif %s {\n", innerIndent, condStr))
|
||||
sb.WriteString(deepIndent)
|
||||
sb.WriteString(g.arrayElementToCUEWithDepth(entry.element, depth+2))
|
||||
sb.WriteString("\n")
|
||||
sb.WriteString(fmt.Sprintf("%s},\n", innerIndent))
|
||||
|
||||
case entryForEach:
|
||||
sourceStr := g.valueToCUE(entry.source)
|
||||
if entry.guard != nil {
|
||||
guardStr := g.conditionToCUE(entry.guard)
|
||||
sb.WriteString(fmt.Sprintf("%sif %s for m in %s {\n", innerIndent, guardStr, sourceStr))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%sfor m in %s {\n", innerIndent, sourceStr))
|
||||
}
|
||||
// Write each field from the element template
|
||||
for key, val := range entry.element.Fields() {
|
||||
valStr := g.valueToCUE(val)
|
||||
sb.WriteString(fmt.Sprintf("%s%s: %s\n", deepIndent, key, valStr))
|
||||
}
|
||||
// Write conditional operations
|
||||
for _, op := range entry.element.Ops() {
|
||||
if setIf, ok := op.(*SetIfOp); ok {
|
||||
condStr := g.conditionToCUE(setIf.Cond())
|
||||
valStr := g.valueToCUE(setIf.Value())
|
||||
cuePath := strings.ReplaceAll(setIf.Path(), ".", ": ")
|
||||
sb.WriteString(fmt.Sprintf("%sif %s {\n", deepIndent, condStr))
|
||||
sb.WriteString(fmt.Sprintf("%s\t%s: %s\n", deepIndent, cuePath, valStr))
|
||||
sb.WriteString(fmt.Sprintf("%s}\n", deepIndent))
|
||||
}
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s},\n", innerIndent))
|
||||
|
||||
case entryForEachWith:
|
||||
sourceStr := g.valueToCUE(entry.source)
|
||||
sb.WriteString(fmt.Sprintf("%sfor %s in %s {\n", innerIndent, entry.itemBuilder.VarName(), sourceStr))
|
||||
g.writeItemBuilderOps(&sb, entry.itemBuilder.Ops(), depth+2)
|
||||
sb.WriteString(fmt.Sprintf("%s},\n", innerIndent))
|
||||
}
|
||||
}
|
||||
|
||||
sb.WriteString(fmt.Sprintf("%s]", indent))
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// writeItemBuilderOps writes the CUE for ItemBuilder operations.
|
||||
func (g *CUEGenerator) writeItemBuilderOps(sb *strings.Builder, ops []itemOp, depth int) {
|
||||
indent := strings.Repeat(g.indent, depth)
|
||||
|
||||
for _, op := range ops {
|
||||
switch o := op.(type) {
|
||||
case setOp:
|
||||
valStr := g.valueToCUE(o.value)
|
||||
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, o.field, valStr))
|
||||
|
||||
case ifBlockOp:
|
||||
condStr := g.conditionToCUE(o.cond)
|
||||
sb.WriteString(fmt.Sprintf("%sif %s {\n", indent, condStr))
|
||||
g.writeItemBuilderOps(sb, o.body, depth+1)
|
||||
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
||||
|
||||
case letOp:
|
||||
valStr := g.valueToCUE(o.value)
|
||||
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, o.name, valStr))
|
||||
|
||||
case setDefaultOp:
|
||||
defStr := g.valueToCUE(o.defValue)
|
||||
sb.WriteString(fmt.Sprintf("%s%s: *%s | %s\n", indent, o.field, defStr, o.typeName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collectionOpToCUE generates CUE for a collection operation.
|
||||
func (g *CUEGenerator) collectionOpToCUE(col *CollectionOp) string {
|
||||
sourceStr := g.valueToCUE(col.Source())
|
||||
@@ -1360,30 +1523,53 @@ func (g *CUEGenerator) collectionOpToCUE(col *CollectionOp) string {
|
||||
}
|
||||
}
|
||||
|
||||
// Build the list comprehension for Map operations
|
||||
// Check if there's a Map operation
|
||||
hasMap := false
|
||||
for _, op := range ops {
|
||||
if _, ok := op.(*mapOp); ok {
|
||||
hasMap = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Build the list comprehension
|
||||
var sb strings.Builder
|
||||
sb.WriteString("[for v in ")
|
||||
sb.WriteString("[")
|
||||
|
||||
// Add guard condition if present (wraps entire comprehension)
|
||||
if guard := col.GetGuard(); guard != nil {
|
||||
guardStr := g.conditionToCUE(guard)
|
||||
sb.WriteString("if ")
|
||||
sb.WriteString(guardStr)
|
||||
sb.WriteString(" ")
|
||||
}
|
||||
|
||||
sb.WriteString("for v in ")
|
||||
sb.WriteString(sourceStr)
|
||||
if filterCondition != "" {
|
||||
sb.WriteString(" if ")
|
||||
sb.WriteString(filterCondition)
|
||||
}
|
||||
sb.WriteString(" {\n")
|
||||
sb.WriteString("\t\t\t\t{\n")
|
||||
|
||||
// Check operations for Map
|
||||
for _, op := range ops {
|
||||
if mOp, ok := op.(*mapOp); ok {
|
||||
for fieldName, fieldVal := range mOp.mappings {
|
||||
valStr := g.fieldValueToCUE(fieldVal)
|
||||
sb.WriteString(fmt.Sprintf("\t\t\t\t\t%s: %s\n", fieldName, valStr))
|
||||
if hasMap {
|
||||
// Map operations: render mapped fields in a struct
|
||||
sb.WriteString(" {\n")
|
||||
sb.WriteString("\t\t\t\t{\n")
|
||||
for _, op := range ops {
|
||||
if mOp, ok := op.(*mapOp); ok {
|
||||
for fieldName, fieldVal := range mOp.mappings {
|
||||
valStr := g.fieldValueToCUE(fieldVal)
|
||||
sb.WriteString(fmt.Sprintf("\t\t\t\t\t%s: %s\n", fieldName, valStr))
|
||||
}
|
||||
}
|
||||
}
|
||||
sb.WriteString("\t\t\t\t}\n")
|
||||
sb.WriteString("\t\t\t}]")
|
||||
} else {
|
||||
// Filter-only: pass through the iteration variable
|
||||
sb.WriteString(" {v}]")
|
||||
}
|
||||
|
||||
sb.WriteString("\t\t\t\t}\n")
|
||||
sb.WriteString("\t\t\t}]")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@@ -1660,12 +1846,7 @@ func (g *CUEGenerator) conditionToCUE(cond Condition) string {
|
||||
case *FalsyCondition:
|
||||
return fmt.Sprintf("!parameter.%s", c.ParamName())
|
||||
case *InCondition:
|
||||
// Generate: parameter.name == val1 || parameter.name == val2 || ...
|
||||
parts := make([]string, len(c.Values()))
|
||||
for i, v := range c.Values() {
|
||||
parts[i] = fmt.Sprintf("parameter.%s == %s", c.ParamName(), formatCUEValue(v))
|
||||
}
|
||||
return strings.Join(parts, " || ")
|
||||
return g.inConditionToCUE(c)
|
||||
case *StringContainsCondition:
|
||||
return fmt.Sprintf(`strings.Contains(parameter.%s, %q)`, c.ParamName(), c.Substr())
|
||||
case *StringMatchesCondition:
|
||||
@@ -1722,6 +1903,16 @@ func (g *CUEGenerator) conditionToCUE(cond Condition) string {
|
||||
// Check if len(source) != 0
|
||||
sourceStr := g.valueToCUE(c.Source())
|
||||
return fmt.Sprintf("len(%s) != 0", sourceStr)
|
||||
case *LenValueCondition:
|
||||
// Check len(source) op n for arbitrary values
|
||||
sourceStr := g.valueToCUE(c.Source())
|
||||
return fmt.Sprintf("len(%s) %s %d", sourceStr, c.Op(), c.Length())
|
||||
case *IterFieldExistsCondition:
|
||||
// Check if iteration variable field exists/not exists
|
||||
if c.IsNegated() {
|
||||
return fmt.Sprintf("%s.%s == _|_", c.VarName(), c.FieldName())
|
||||
}
|
||||
return fmt.Sprintf("%s.%s != _|_", c.VarName(), c.FieldName())
|
||||
case *PathExistsCondition:
|
||||
// Check if a path exists: path != _|_
|
||||
return fmt.Sprintf("%s != _|_", c.Path())
|
||||
@@ -1751,6 +1942,16 @@ func (g *CUEGenerator) conditionToCUE(cond Condition) string {
|
||||
}
|
||||
}
|
||||
|
||||
// inConditionToCUE converts an InCondition to CUE syntax.
|
||||
// Generates: parameter.name == val1 || parameter.name == val2 || ...
|
||||
func (g *CUEGenerator) inConditionToCUE(c *InCondition) string {
|
||||
parts := make([]string, len(c.Values()))
|
||||
for i, v := range c.Values() {
|
||||
parts[i] = fmt.Sprintf("parameter.%s == %s", c.ParamName(), formatCUEValue(v))
|
||||
}
|
||||
return strings.Join(parts, " || ")
|
||||
}
|
||||
|
||||
// exprToCUE converts an Expr to CUE syntax.
|
||||
func (g *CUEGenerator) exprToCUE(e Expr) string {
|
||||
if v, ok := e.(Value); ok {
|
||||
@@ -2092,6 +2293,14 @@ func (g *CUEGenerator) writeMapParam(sb *strings.Builder, p *MapParam, indent, n
|
||||
g.writeParam(sb, field, depth+1)
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
||||
} else if valType := p.ValueType(); valType != "" {
|
||||
// Typed map: [string]: type
|
||||
cueType := g.cueTypeForParamType(valType)
|
||||
if cueType != "" {
|
||||
sb.WriteString(fmt.Sprintf("%s%s%s: [string]: %s\n", indent, name, optional, cueType))
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("%s%s%s: {...}\n", indent, name, optional))
|
||||
}
|
||||
} else {
|
||||
// Generic object
|
||||
sb.WriteString(fmt.Sprintf("%s%s%s: {...}\n", indent, name, optional))
|
||||
@@ -2133,15 +2342,38 @@ func (g *CUEGenerator) writeStructField(sb *strings.Builder, f *StructField, dep
|
||||
|
||||
fieldType := g.cueTypeForParamType(f.FieldType())
|
||||
|
||||
if nested := f.GetNested(); nested != nil {
|
||||
nested := f.GetNested()
|
||||
switch {
|
||||
case nested != nil:
|
||||
sb.WriteString(fmt.Sprintf("%s%s%s: {\n", indent, name, optional))
|
||||
for _, nestedField := range nested.GetFields() {
|
||||
g.writeStructField(sb, nestedField, depth+1)
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
||||
} else if f.HasDefault() {
|
||||
sb.WriteString(fmt.Sprintf("%s%s: *%v | %s\n", indent, name, formatCUEValue(f.GetDefault()), fieldType))
|
||||
} else {
|
||||
case f.HasDefault():
|
||||
enumValues := f.GetEnumValues()
|
||||
switch {
|
||||
case len(enumValues) > 0:
|
||||
// Enum with default: *"default" | "other1" | "other2"
|
||||
defaultStr := fmt.Sprintf("%v", f.GetDefault())
|
||||
var enumParts []string
|
||||
enumParts = append(enumParts, fmt.Sprintf("*%s", formatCUEValue(f.GetDefault())))
|
||||
for _, v := range enumValues {
|
||||
if v != defaultStr {
|
||||
enumParts = append(enumParts, fmt.Sprintf("%q", v))
|
||||
}
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, name, strings.Join(enumParts, " | ")))
|
||||
case f.FieldType() == ParamTypeArray && f.GetElementType() != "":
|
||||
elemCUE := g.cueTypeForParamType(f.GetElementType())
|
||||
sb.WriteString(fmt.Sprintf("%s%s: *%v | [...%s]\n", indent, name, formatCUEValue(f.GetDefault()), elemCUE))
|
||||
default:
|
||||
sb.WriteString(fmt.Sprintf("%s%s: *%v | %s\n", indent, name, formatCUEValue(f.GetDefault()), fieldType))
|
||||
}
|
||||
case f.FieldType() == ParamTypeArray && f.GetElementType() != "":
|
||||
elemCUE := g.cueTypeForParamType(f.GetElementType())
|
||||
sb.WriteString(fmt.Sprintf("%s%s%s: [...%s]\n", indent, name, optional, elemCUE))
|
||||
default:
|
||||
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, optional, fieldType))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,11 +459,21 @@ func PathExists(path string) *PathExistsCondition {
|
||||
|
||||
// --- Array Element Struct ---
|
||||
|
||||
// patchKeyField represents a field within an ArrayElement that has a patchKey annotation.
|
||||
// This is used for nested patchKey annotations inside array elements,
|
||||
// e.g., volumeMounts inside a containers element.
|
||||
type patchKeyField struct {
|
||||
field string // field name (e.g., "volumeMounts")
|
||||
key string // patchKey value (e.g., "name")
|
||||
value Value // the array value
|
||||
}
|
||||
|
||||
// ArrayElement represents a single element in an array patch.
|
||||
// Used for building array values with struct elements.
|
||||
type ArrayElement struct {
|
||||
fields map[string]Value
|
||||
ops []ResourceOp // nested operations for complex structs
|
||||
fields map[string]Value
|
||||
ops []ResourceOp // nested operations for complex structs
|
||||
patchKeyFields []patchKeyField // nested patchKey-annotated array fields
|
||||
}
|
||||
|
||||
func (a *ArrayElement) expr() {}
|
||||
@@ -489,12 +499,28 @@ func (a *ArrayElement) SetIf(cond Condition, key string, value Value) *ArrayElem
|
||||
return a
|
||||
}
|
||||
|
||||
// PatchKeyField adds a patchKey-annotated array field to the array element.
|
||||
// This generates within the element:
|
||||
//
|
||||
// // +patchKey=key
|
||||
// field: value
|
||||
//
|
||||
// Used for nested patchKey annotations inside array elements, e.g.,
|
||||
// volumeMounts with patchKey=name inside a containers element.
|
||||
func (a *ArrayElement) PatchKeyField(field string, key string, value Value) *ArrayElement {
|
||||
a.patchKeyFields = append(a.patchKeyFields, patchKeyField{field: field, key: key, value: value})
|
||||
return a
|
||||
}
|
||||
|
||||
// Fields returns all fields set on this element.
|
||||
func (a *ArrayElement) Fields() map[string]Value { return a.fields }
|
||||
|
||||
// Ops returns any conditional operations.
|
||||
func (a *ArrayElement) Ops() []ResourceOp { return a.ops }
|
||||
|
||||
// PatchKeyFields returns the patchKey-annotated fields.
|
||||
func (a *ArrayElement) PatchKeyFields() []patchKeyField { return a.patchKeyFields }
|
||||
|
||||
// --- Reference Expressions ---
|
||||
|
||||
// Ref creates a raw reference expression.
|
||||
@@ -582,6 +608,68 @@ func ForEachMap() *ForEachMapOp {
|
||||
}
|
||||
}
|
||||
|
||||
// --- CUE String Interpolation ---
|
||||
|
||||
// InterpolatedString represents a CUE string interpolation expression.
|
||||
// Literal string parts are inlined, Value parts are wrapped in \(...).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// Interpolation(vela.Namespace(), Lit(":"), name)
|
||||
// // Generates: "\(context.namespace):\(parameter.name)"
|
||||
type InterpolatedString struct {
|
||||
parts []Value
|
||||
}
|
||||
|
||||
func (i *InterpolatedString) value() {}
|
||||
func (i *InterpolatedString) expr() {}
|
||||
|
||||
// Parts returns the interpolation parts.
|
||||
func (i *InterpolatedString) Parts() []Value { return i.parts }
|
||||
|
||||
// Interpolation creates a CUE string interpolation expression.
|
||||
// Literal string values are inlined directly. All other values are
|
||||
// wrapped in \(...) interpolation syntax.
|
||||
func Interpolation(parts ...Value) *InterpolatedString {
|
||||
return &InterpolatedString{parts: parts}
|
||||
}
|
||||
|
||||
// --- LenValueCondition ---
|
||||
|
||||
// LenValueCondition checks the length of an arbitrary Value (not just a parameter).
|
||||
// This extends LenCondition to work with let variables and other expressions.
|
||||
// Generates: len(source) op n
|
||||
type LenValueCondition struct {
|
||||
baseCondition
|
||||
source Value
|
||||
op string // ==, !=, <, <=, >, >=
|
||||
length int
|
||||
}
|
||||
|
||||
// Source returns the source value being measured.
|
||||
func (c *LenValueCondition) Source() Value { return c.source }
|
||||
|
||||
// Op returns the comparison operator.
|
||||
func (c *LenValueCondition) Op() string { return c.op }
|
||||
|
||||
// Length returns the length to compare against.
|
||||
func (c *LenValueCondition) Length() int { return c.length }
|
||||
|
||||
// LenGt creates a condition: len(source) > n.
|
||||
func LenGt(source Value, n int) *LenValueCondition {
|
||||
return &LenValueCondition{source: source, op: ">", length: n}
|
||||
}
|
||||
|
||||
// LenGe creates a condition: len(source) >= n.
|
||||
func LenGe(source Value, n int) *LenValueCondition {
|
||||
return &LenValueCondition{source: source, op: ">=", length: n}
|
||||
}
|
||||
|
||||
// LenEq creates a condition: len(source) == n.
|
||||
func LenEq(source Value, n int) *LenValueCondition {
|
||||
return &LenValueCondition{source: source, op: "==", length: n}
|
||||
}
|
||||
|
||||
// Over specifies the source to iterate over.
|
||||
func (f *ForEachMapOp) Over(source string) *ForEachMapOp {
|
||||
f.source = source
|
||||
@@ -612,3 +700,80 @@ func (f *ForEachMapOp) WithBody(ops ...ResourceOp) *ForEachMapOp {
|
||||
f.body = append(f.body, ops...)
|
||||
return f
|
||||
}
|
||||
|
||||
// PlusExpr represents a + operator between multiple values.
|
||||
// Generates CUE: a + b + c
|
||||
// Works for string concatenation, array concatenation, etc.
|
||||
type PlusExpr struct {
|
||||
parts []Value
|
||||
}
|
||||
|
||||
func (p *PlusExpr) value() {}
|
||||
func (p *PlusExpr) expr() {}
|
||||
|
||||
// Parts returns the operands.
|
||||
func (p *PlusExpr) Parts() []Value { return p.parts }
|
||||
|
||||
// Plus creates a + expression between values.
|
||||
// Generates CUE: parts[0] + parts[1] + ...
|
||||
func Plus(parts ...Value) *PlusExpr {
|
||||
return &PlusExpr{parts: parts}
|
||||
}
|
||||
|
||||
// IterFieldRef references a field on the iteration variable.
|
||||
// Generates CUE: v.fieldName (where v is the iteration variable).
|
||||
type IterFieldRef struct {
|
||||
varName string
|
||||
field string
|
||||
}
|
||||
|
||||
func (r *IterFieldRef) value() {}
|
||||
func (r *IterFieldRef) expr() {}
|
||||
|
||||
// VarName returns the iteration variable name.
|
||||
func (r *IterFieldRef) VarName() string { return r.varName }
|
||||
|
||||
// FieldName returns the field name.
|
||||
func (r *IterFieldRef) FieldName() string { return r.field }
|
||||
|
||||
// IterVarRef references the iteration variable itself (not a field on it).
|
||||
// Generates CUE: v (where v is the iteration variable).
|
||||
type IterVarRef struct {
|
||||
varName string
|
||||
}
|
||||
|
||||
func (r *IterVarRef) value() {}
|
||||
func (r *IterVarRef) expr() {}
|
||||
|
||||
// VarName returns the iteration variable name.
|
||||
func (r *IterVarRef) VarName() string { return r.varName }
|
||||
|
||||
// IterLetRef references a let binding defined inside an iteration body.
|
||||
// Generates CUE: _name (a private CUE identifier).
|
||||
type IterLetRef struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (r *IterLetRef) value() {}
|
||||
func (r *IterLetRef) expr() {}
|
||||
|
||||
// RefName returns the binding name.
|
||||
func (r *IterLetRef) RefName() string { return r.name }
|
||||
|
||||
// IterFieldExistsCondition checks if an iteration variable field exists.
|
||||
// Generates CUE: v.field != _|_ (or v.field == _|_ when negated).
|
||||
type IterFieldExistsCondition struct {
|
||||
baseCondition
|
||||
varName string
|
||||
field string
|
||||
negate bool
|
||||
}
|
||||
|
||||
// VarName returns the iteration variable name.
|
||||
func (c *IterFieldExistsCondition) VarName() string { return c.varName }
|
||||
|
||||
// FieldName returns the field name.
|
||||
func (c *IterFieldExistsCondition) FieldName() string { return c.field }
|
||||
|
||||
// IsNegated returns true if this is a "not exists" check.
|
||||
func (c *IterFieldExistsCondition) IsNegated() bool { return c.negate }
|
||||
|
||||
@@ -801,6 +801,7 @@ type StructField struct {
|
||||
nested *StructParam // for nested structs
|
||||
schemaRef string // reference to a helper definition (e.g., "HealthProbe")
|
||||
enumValues []string // allowed enum values for string fields
|
||||
elementType ParamType // for array fields: element type (e.g., ParamTypeString for [...string])
|
||||
}
|
||||
|
||||
// Field creates a new struct field definition.
|
||||
@@ -883,6 +884,15 @@ func (f *StructField) Enum(values ...string) *StructField {
|
||||
// GetEnumValues returns the allowed enum values.
|
||||
func (f *StructField) GetEnumValues() []string { return f.enumValues }
|
||||
|
||||
// ArrayOf sets the element type for array fields (e.g., ParamTypeString for [...string]).
|
||||
func (f *StructField) ArrayOf(elemType ParamType) *StructField {
|
||||
f.elementType = elemType
|
||||
return f
|
||||
}
|
||||
|
||||
// GetElementType returns the element type for array fields.
|
||||
func (f *StructField) GetElementType() ParamType { return f.elementType }
|
||||
|
||||
// StructParam represents a structured parameter with named fields.
|
||||
type StructParam struct {
|
||||
baseParam
|
||||
|
||||
@@ -407,12 +407,8 @@ func (g *TraitCUEGenerator) GenerateFullDefinition(t *TraitDefinition) string {
|
||||
sb.WriteString(")\n\n")
|
||||
}
|
||||
|
||||
// Write trait header - quote names with special characters
|
||||
name := t.GetName()
|
||||
if strings.ContainsAny(name, "-./") {
|
||||
name = fmt.Sprintf("%q", name)
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s: {\n", name))
|
||||
// Write trait header
|
||||
sb.WriteString(fmt.Sprintf("%s: {\n", cueLabel(t.GetName())))
|
||||
sb.WriteString(fmt.Sprintf("%stype: \"trait\"\n", g.indent))
|
||||
sb.WriteString(fmt.Sprintf("%sannotations: {}\n", g.indent))
|
||||
|
||||
@@ -556,6 +552,12 @@ func (g *TraitCUEGenerator) writeUnifiedTemplate(sb *strings.Builder, t *TraitDe
|
||||
return
|
||||
}
|
||||
|
||||
// Render let bindings before patch/outputs
|
||||
for _, lb := range tpl.GetLetBindings() {
|
||||
exprStr := gen.valueToCUE(lb.Expr())
|
||||
sb.WriteString(fmt.Sprintf("%slet %s = %s\n", indent, lb.Name(), exprStr))
|
||||
}
|
||||
|
||||
// Generate patch block if present
|
||||
if tpl.HasPatch() {
|
||||
// Write patch strategy comment if set
|
||||
@@ -572,7 +574,16 @@ func (g *TraitCUEGenerator) writeUnifiedTemplate(sb *strings.Builder, t *TraitDe
|
||||
if outputs := tpl.GetOutputs(); len(outputs) > 0 {
|
||||
sb.WriteString(fmt.Sprintf("%soutputs: {\n", indent))
|
||||
for name, res := range outputs {
|
||||
g.writeResourceOutput(sb, gen, name, res, depth+1)
|
||||
g.writeTraitResourceOutput(sb, gen, name, res, depth+1)
|
||||
}
|
||||
// Render output groups (multiple outputs under one condition)
|
||||
for _, group := range tpl.GetOutputGroups() {
|
||||
condStr := gen.conditionToCUE(group.cond)
|
||||
sb.WriteString(fmt.Sprintf("%s\tif %s {\n", indent, condStr))
|
||||
for gName, gRes := range group.outputs {
|
||||
g.writeTraitResourceOutput(sb, gen, gName, gRes, depth+2)
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s\t}\n", indent))
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
||||
}
|
||||
@@ -817,20 +828,11 @@ func (g *TraitCUEGenerator) writePatchKeyOp(sb *strings.Builder, gen *CUEGenerat
|
||||
}
|
||||
}
|
||||
|
||||
// writeResourceOutput writes a resource as CUE (reusing component generation logic).
|
||||
func (g *TraitCUEGenerator) writeResourceOutput(sb *strings.Builder, gen *CUEGenerator, name string, res *Resource, depth int) {
|
||||
indent := strings.Repeat(g.indent, depth)
|
||||
innerIndent := strings.Repeat(g.indent, depth+1)
|
||||
|
||||
sb.WriteString(fmt.Sprintf("%s%s: {\n", indent, name))
|
||||
sb.WriteString(fmt.Sprintf("%sapiVersion: %q\n", innerIndent, res.APIVersion()))
|
||||
sb.WriteString(fmt.Sprintf("%skind: %q\n", innerIndent, res.Kind()))
|
||||
|
||||
// Build field tree and write it
|
||||
tree := gen.buildFieldTree(res.Ops())
|
||||
gen.writeFieldTree(sb, tree, depth+1)
|
||||
|
||||
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
||||
// writeTraitResourceOutput writes a resource as CUE for trait outputs.
|
||||
// This handles OutputsIf conditions and VersionConditionals, which the old
|
||||
// writeResourceOutput method did not support.
|
||||
func (g *TraitCUEGenerator) writeTraitResourceOutput(sb *strings.Builder, gen *CUEGenerator, name string, res *Resource, depth int) {
|
||||
gen.writeResourceOutput(sb, name, res, res.outputCondition, depth)
|
||||
}
|
||||
|
||||
// generateParameterBlock generates the parameter schema for the trait.
|
||||
@@ -1225,12 +1227,8 @@ func (g *TraitCUEGenerator) GenerateDefinitionWithRawTemplate(t *TraitDefinition
|
||||
sb.WriteString(")\n\n")
|
||||
}
|
||||
|
||||
// Write trait header - quote names with special characters
|
||||
name := t.GetName()
|
||||
if strings.ContainsAny(name, "-./") {
|
||||
name = fmt.Sprintf("%q", name)
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("%s: {\n", name))
|
||||
// Write trait header
|
||||
sb.WriteString(fmt.Sprintf("%s: {\n", cueLabel(t.GetName())))
|
||||
sb.WriteString(fmt.Sprintf("%stype: \"trait\"\n", g.indent))
|
||||
sb.WriteString(fmt.Sprintf("%sannotations: {}\n", g.indent))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user