mirror of
https://github.com/kubevela/kubevela.git
synced 2026-08-18 03:56:36 +00:00
* Fix: Enhance CUE generation for optional fields in collection operations Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Feat: Add tests for optional fields and conditional logic in trait definitions Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add namespace field to podAffinityTerm in affinity trait definition Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add support for ForEachMap operation in CUE generation Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add tests for ForEachMap let bindings and custom rendering in trait definitions Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add optional description fields for PatchContainer and enhance parameter generation logic Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Update parameter access to bracket notation in CUE generation and tests Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Implement PatchStrategyAnnotation for CUE generation and enhance condition hoisting logic Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add SpreadAll operation for array patches and enhance IntParam constraints handling Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Refactor writeSpreadAllOp to improve condition handling and element processing Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Correct nodeSelector spelling and references in affinity trait definition Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add optional namespace field to podAffinityTerm in affinity trait definition Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * test: re-trigger test pipelines Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> Co-authored-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> * ci: retrigger checks Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> Co-authored-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> * Fix: Update workflow API import paths to use the correct package location Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> Co-authored-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Update dependency versions in go.mod for improved compatibility Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> Co-authored-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> * ci: retrigger checks Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> Co-authored-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add tests for SpreadAll operation and conditional handling in trait definitions Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Enhance optional field handling for non-string conditions in PatchContainer Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Implement ForceOptional parameter handling to retain optionality with defaults Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Add PatchStrategy field to PatchContainerConfig and update related logic in trait definitions Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Implement PatchFieldBuilder for fluent API construction of PatchContainerField Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> * Fix: Update test descriptions and enhance CUE generation for PatchFields with NotEmpty and IsSet conditions Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> --------- Signed-off-by: Ayush Kumar <ayushshyamkumar888@gmail.com> Signed-off-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com> Co-authored-by: Chaitanya Reddy Onteddu <chaitanyareddy0702@gmail.com>
2673 lines
87 KiB
Go
2673 lines
87 KiB
Go
/*
|
|
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
|
|
|
|
import (
|
|
"fmt"
|
|
"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
|
|
imports []string
|
|
}
|
|
|
|
// CUEImports defines standard imports that may be needed in CUE definitions.
|
|
var CUEImports = struct {
|
|
Strconv string
|
|
Strings string
|
|
List string
|
|
Math string
|
|
Regexp string
|
|
Time string
|
|
Struct string
|
|
Encoding string
|
|
}{
|
|
Strconv: "strconv",
|
|
Strings: "strings",
|
|
List: "list",
|
|
Math: "math",
|
|
Regexp: "regexp",
|
|
Time: "time",
|
|
Struct: "struct",
|
|
Encoding: "encoding/json",
|
|
}
|
|
|
|
// cueBoolTrue is the CUE literal for a true boolean value.
|
|
const cueBoolTrue = "true"
|
|
|
|
// ImportRequirer is implemented by types that require CUE imports.
|
|
// This allows the CUE generator to automatically detect and add required imports.
|
|
type ImportRequirer interface {
|
|
// RequiredImports returns the list of CUE imports this type requires.
|
|
RequiredImports() []string
|
|
}
|
|
|
|
// NewCUEGenerator creates a new CUE generator.
|
|
func NewCUEGenerator() *CUEGenerator {
|
|
return &CUEGenerator{
|
|
indent: "\t",
|
|
imports: []string{},
|
|
}
|
|
}
|
|
|
|
// WithImports adds CUE imports to the generator.
|
|
// Usage: gen.WithImports(CUEImports.Strconv, CUEImports.Strings)
|
|
func (g *CUEGenerator) WithImports(imports ...string) *CUEGenerator {
|
|
g.imports = append(g.imports, imports...)
|
|
return g
|
|
}
|
|
|
|
// detectRequiredImports analyzes the component template and automatically adds
|
|
// any required CUE standard library imports by checking all values for ImportRequirer.
|
|
func (g *CUEGenerator) detectRequiredImports(c *ComponentDefinition) {
|
|
// Execute the template to capture what constructs are used
|
|
tpl := NewTemplate()
|
|
if templateFn := c.GetTemplate(); templateFn != nil {
|
|
templateFn(tpl)
|
|
}
|
|
|
|
// Collect imports from all template elements using the ImportRequirer interface
|
|
// This is extensible - any type that implements ImportRequirer will have its imports added
|
|
|
|
// Check concat helpers (list.Concat requires "list")
|
|
for _, helper := range tpl.GetConcatHelpers() {
|
|
g.collectImportsFromValue(helper)
|
|
}
|
|
|
|
// Check struct array helpers
|
|
for _, helper := range tpl.GetStructArrayHelpers() {
|
|
g.collectImportsFromValue(helper)
|
|
}
|
|
|
|
// Check dedupe helpers
|
|
for _, helper := range tpl.GetDedupeHelpers() {
|
|
g.collectImportsFromValue(helper)
|
|
}
|
|
|
|
// Check helper variables (before and after output)
|
|
for _, helper := range tpl.GetHelpersBeforeOutput() {
|
|
g.collectImportsFromValue(helper)
|
|
g.collectImportsFromValue(helper.Collection())
|
|
}
|
|
for _, helper := range tpl.GetHelpersAfterOutput() {
|
|
g.collectImportsFromValue(helper)
|
|
g.collectImportsFromValue(helper.Collection())
|
|
}
|
|
|
|
// Check resource operations in output
|
|
if output := tpl.GetOutput(); output != nil {
|
|
g.collectImportsFromResource(output)
|
|
}
|
|
|
|
// Check resource operations in outputs
|
|
for _, res := range tpl.GetOutputs() {
|
|
g.collectImportsFromResource(res)
|
|
}
|
|
}
|
|
|
|
// addImportIfMissing adds an import if it's not already present.
|
|
func (g *CUEGenerator) addImportIfMissing(imp string) {
|
|
for _, existing := range g.imports {
|
|
if existing == imp {
|
|
return
|
|
}
|
|
}
|
|
g.imports = append(g.imports, imp)
|
|
}
|
|
|
|
// collectImportsFromValue checks if a value implements ImportRequirer and adds its imports.
|
|
// It also recursively checks nested values.
|
|
func (g *CUEGenerator) collectImportsFromValue(v interface{}) {
|
|
if v == nil {
|
|
return
|
|
}
|
|
|
|
// Check if the value itself requires imports
|
|
if ir, ok := v.(ImportRequirer); ok {
|
|
for _, imp := range ir.RequiredImports() {
|
|
g.addImportIfMissing(imp)
|
|
}
|
|
}
|
|
|
|
// Recursively check nested structures
|
|
switch val := v.(type) {
|
|
case *CollectionOp:
|
|
g.collectImportsFromValue(val.Source())
|
|
for _, op := range val.Operations() {
|
|
if mOp, ok := op.(*mapOp); ok {
|
|
for _, fv := range mOp.mappings {
|
|
g.collectImportsFromFieldValue(fv)
|
|
}
|
|
}
|
|
}
|
|
case *MultiSource:
|
|
g.collectImportsFromValue(val.Source())
|
|
for _, fm := range val.MapBySourceMappings() {
|
|
for _, fv := range fm {
|
|
g.collectImportsFromFieldValue(fv)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// collectImportsFromFieldValue checks field values for import requirements.
|
|
func (g *CUEGenerator) collectImportsFromFieldValue(fv FieldValue) {
|
|
if fv == nil {
|
|
return
|
|
}
|
|
|
|
// Check if the field value requires imports
|
|
if ir, ok := fv.(ImportRequirer); ok {
|
|
for _, imp := range ir.RequiredImports() {
|
|
g.addImportIfMissing(imp)
|
|
}
|
|
}
|
|
|
|
// Check nested field values
|
|
switch val := fv.(type) {
|
|
case *OrFieldRef:
|
|
g.collectImportsFromFieldValue(val.fallback)
|
|
case *NestedField:
|
|
for _, nested := range val.mapping {
|
|
g.collectImportsFromFieldValue(nested)
|
|
}
|
|
}
|
|
}
|
|
|
|
// collectImportsFromResource checks all operations in a resource for import requirements.
|
|
func (g *CUEGenerator) collectImportsFromResource(res *Resource) {
|
|
if res == nil {
|
|
return
|
|
}
|
|
|
|
for _, op := range res.Ops() {
|
|
switch o := op.(type) {
|
|
case *SetOp:
|
|
g.collectImportsFromValue(o.Value())
|
|
case *SetIfOp:
|
|
g.collectImportsFromValue(o.Value())
|
|
case *SpreadIfOp:
|
|
g.collectImportsFromValue(o.Value())
|
|
case *IfBlock:
|
|
for _, innerOp := range o.Ops() {
|
|
switch inner := innerOp.(type) {
|
|
case *SetOp:
|
|
g.collectImportsFromValue(inner.Value())
|
|
case *SetIfOp:
|
|
g.collectImportsFromValue(inner.Value())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// GenerateParameterSchema generates the CUE parameter schema from a component definition.
|
|
// This generates only the `parameter: { ... }` block for comparison with original CUE.
|
|
func (g *CUEGenerator) GenerateParameterSchema(c *ComponentDefinition) string {
|
|
var sb strings.Builder
|
|
sb.WriteString("parameter: {\n")
|
|
|
|
for _, param := range c.GetParams() {
|
|
g.writeParam(&sb, param, 1)
|
|
}
|
|
|
|
sb.WriteString("}\n")
|
|
return sb.String()
|
|
}
|
|
|
|
// GenerateFullDefinition generates the complete CUE definition from a component.
|
|
func (g *CUEGenerator) GenerateFullDefinition(c *ComponentDefinition) string {
|
|
// Auto-detect required imports from template
|
|
g.detectRequiredImports(c)
|
|
|
|
var sb strings.Builder
|
|
|
|
// Write imports if any
|
|
if len(g.imports) > 0 {
|
|
sb.WriteString("import (\n")
|
|
for _, imp := range g.imports {
|
|
sb.WriteString(fmt.Sprintf("\t%q\n", imp))
|
|
}
|
|
sb.WriteString(")\n\n")
|
|
}
|
|
|
|
// 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))
|
|
sb.WriteString(fmt.Sprintf("%sdescription: %q\n", g.indent, c.GetDescription()))
|
|
|
|
// Write attributes
|
|
sb.WriteString(fmt.Sprintf("%sattributes: {\n", g.indent))
|
|
g.writeWorkload(&sb, c, 2)
|
|
g.writeStatus(&sb, c, 2)
|
|
sb.WriteString(fmt.Sprintf("%s}\n", g.indent))
|
|
|
|
sb.WriteString("}\n")
|
|
|
|
// Write template section (includes parameter inside)
|
|
sb.WriteString(g.GenerateTemplate(c))
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
// GenerateTemplate generates the CUE template block from a component's template function.
|
|
// The template block contains: helpers, output, outputs, parameter, and helper definitions.
|
|
// The order follows KubeVela conventions:
|
|
// 1. Primary helpers (before output) - mountsArray, volumesList, deDupVolumesList
|
|
// 2. output: block - the main resource
|
|
// 3. Auxiliary helpers (after output) - exposePorts and similar helpers used by outputs
|
|
// 4. outputs: block - auxiliary resources like Service
|
|
// 5. parameter: block - parameter schema
|
|
// 6. Helper type definitions - #HealthProbe and similar
|
|
func (g *CUEGenerator) GenerateTemplate(c *ComponentDefinition) string {
|
|
var sb strings.Builder
|
|
sb.WriteString("template: {\n")
|
|
|
|
// Execute the template function to capture operations
|
|
tpl := NewTemplate()
|
|
if templateFn := c.GetTemplate(); templateFn != nil {
|
|
templateFn(tpl)
|
|
}
|
|
|
|
// Generate struct-based array helpers first (mountsArray, volumesArray patterns)
|
|
for _, helper := range tpl.GetStructArrayHelpers() {
|
|
g.writeStructArrayHelper(&sb, helper, 1)
|
|
}
|
|
|
|
// Generate concat helpers (list.Concat patterns)
|
|
for _, helper := range tpl.GetConcatHelpers() {
|
|
g.writeConcatHelper(&sb, helper, 1)
|
|
}
|
|
|
|
// Generate dedupe helpers (deDupVolumesArray pattern)
|
|
for _, helper := range tpl.GetDedupeHelpers() {
|
|
g.writeDedupeHelper(&sb, helper, 1)
|
|
}
|
|
|
|
// Generate legacy helper definitions that appear BEFORE output
|
|
for _, helper := range tpl.GetHelpersBeforeOutput() {
|
|
g.writeHelper(&sb, helper, 1)
|
|
}
|
|
|
|
// Generate output block
|
|
if output := tpl.GetOutput(); output != nil {
|
|
g.writeResourceOutput(&sb, "output", output, nil, 1)
|
|
}
|
|
|
|
// Generate helper definitions that appear AFTER output (used by outputs)
|
|
// This matches KubeVela convention where exposePorts appears between output and outputs
|
|
for _, helper := range tpl.GetHelpersAfterOutput() {
|
|
g.writeHelper(&sb, helper, 1)
|
|
}
|
|
|
|
// Generate outputs block for auxiliary resources
|
|
if outputs := tpl.GetOutputs(); len(outputs) > 0 {
|
|
sb.WriteString(fmt.Sprintf("%soutputs: {\n", g.indent))
|
|
for name, res := range outputs {
|
|
g.writeResourceOutput(&sb, name, res, res.outputCondition, 2)
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s}\n", g.indent))
|
|
}
|
|
|
|
// Generate parameter section INSIDE template block (KubeVela convention)
|
|
sb.WriteString(g.generateParameterBlock(c, 1))
|
|
|
|
// Generate helper type definitions (like #HealthProbe)
|
|
for _, helperDef := range c.GetHelperDefinitions() {
|
|
g.WriteHelperDefinition(&sb, helperDef, 1)
|
|
}
|
|
|
|
sb.WriteString("}\n")
|
|
return sb.String()
|
|
}
|
|
|
|
// generateParameterBlock generates the parameter schema at the specified depth.
|
|
func (g *CUEGenerator) generateParameterBlock(c *ComponentDefinition, depth int) string {
|
|
var sb strings.Builder
|
|
indent := strings.Repeat(g.indent, depth)
|
|
|
|
sb.WriteString(fmt.Sprintf("%sparameter: {\n", indent))
|
|
|
|
for _, param := range c.GetParams() {
|
|
g.writeParam(&sb, param, depth+1)
|
|
}
|
|
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
return sb.String()
|
|
}
|
|
|
|
// WriteHelperDefinition writes a CUE helper type definition like #HealthProbe.
|
|
// This method is exported so it can be used by policy and workflow step generators.
|
|
func (g *CUEGenerator) WriteHelperDefinition(sb *strings.Builder, def HelperDefinition, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
|
|
if def.HasParam() {
|
|
// Generate schema from Param using fluent API
|
|
sb.WriteString(fmt.Sprintf("%s#%s: ", indent, def.GetName()))
|
|
g.writeHelperDefFromParam(sb, def.GetParam(), depth)
|
|
} else {
|
|
// Legacy: raw CUE schema
|
|
sb.WriteString(fmt.Sprintf("%s#%s: %s\n", indent, def.GetName(), def.GetSchema()))
|
|
}
|
|
}
|
|
|
|
// writeHelperDefFromParam writes a helper definition schema from a Param.
|
|
func (g *CUEGenerator) writeHelperDefFromParam(sb *strings.Builder, param Param, depth int) {
|
|
switch p := param.(type) {
|
|
case *StructParam:
|
|
// For struct types defined with defkit.Struct(), write the fields as a struct schema
|
|
fields := p.GetFields()
|
|
if len(fields) > 0 {
|
|
sb.WriteString("{\n")
|
|
for _, field := range fields {
|
|
g.writeStructFieldForHelper(sb, field, depth+1)
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s}\n", strings.Repeat(g.indent, depth)))
|
|
} else {
|
|
sb.WriteString("{}\n")
|
|
}
|
|
case *MapParam:
|
|
// For objects, write the fields directly as a struct
|
|
fields := p.GetFields()
|
|
if len(fields) > 0 {
|
|
sb.WriteString("{\n")
|
|
for _, field := range fields {
|
|
g.writeParam(sb, field, depth+1)
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s}\n", strings.Repeat(g.indent, depth)))
|
|
} else {
|
|
sb.WriteString("{}\n")
|
|
}
|
|
case *ArrayParam:
|
|
// For arrays, write as [...{fields}]
|
|
fields := p.GetFields()
|
|
if len(fields) > 0 {
|
|
sb.WriteString("[...{\n")
|
|
for _, field := range fields {
|
|
g.writeParam(sb, field, depth+1)
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s}]\n", strings.Repeat(g.indent, depth)))
|
|
} else {
|
|
sb.WriteString("[...]\n")
|
|
}
|
|
case *IntParam:
|
|
// For int types with optional constraints: int & >=1 & <=65535
|
|
var constraints []string
|
|
if minVal := p.GetMin(); minVal != nil {
|
|
constraints = append(constraints, fmt.Sprintf(">=%d", *minVal))
|
|
}
|
|
if maxVal := p.GetMax(); maxVal != nil {
|
|
constraints = append(constraints, fmt.Sprintf("<=%d", *maxVal))
|
|
}
|
|
if len(constraints) > 0 {
|
|
sb.WriteString(fmt.Sprintf("int & %s\n", strings.Join(constraints, " & ")))
|
|
} else {
|
|
sb.WriteString("int\n")
|
|
}
|
|
default:
|
|
// Fallback for other types
|
|
sb.WriteString("_\n")
|
|
}
|
|
}
|
|
|
|
// writeStructFieldForHelper writes a struct field specifically for helper definitions.
|
|
// This handles StructField objects which are different from Param objects.
|
|
func (g *CUEGenerator) writeStructFieldForHelper(sb *strings.Builder, f *StructField, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
|
|
// Write description as comment if present
|
|
if desc := f.GetDescription(); desc != "" {
|
|
sb.WriteString(fmt.Sprintf("%s// +usage=%s\n", indent, desc))
|
|
}
|
|
|
|
name := f.Name()
|
|
optional := "?"
|
|
if f.IsRequired() {
|
|
optional = ""
|
|
}
|
|
|
|
// Check if this field references another helper type
|
|
if schemaRef := f.GetSchemaRef(); schemaRef != "" {
|
|
// Reference to a helper definition like #ResourcePolicyRuleSelector
|
|
// Check if this is an array type - if so, output [...#SchemaRef]
|
|
if f.FieldType() == ParamTypeArray {
|
|
if f.HasDefault() {
|
|
sb.WriteString(fmt.Sprintf("%s%s: *%v | [...#%s]\n", indent, name, formatCUEValue(f.GetDefault()), schemaRef))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: [...#%s]\n", indent, name, optional, schemaRef))
|
|
}
|
|
} else {
|
|
if f.HasDefault() {
|
|
sb.WriteString(fmt.Sprintf("%s%s: *%v | #%s\n", indent, name, formatCUEValue(f.GetDefault()), schemaRef))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: #%s\n", indent, name, optional, schemaRef))
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Check for nested struct (or array of structs)
|
|
if nested := f.GetNested(); nested != nil {
|
|
if f.FieldType() == ParamTypeArray {
|
|
// Array of structs: [...{fields}]
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: [...{\n", indent, name, optional))
|
|
for _, nestedField := range nested.GetFields() {
|
|
g.writeStructFieldForHelper(sb, nestedField, depth+1)
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s}]\n", indent))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: {\n", indent, name, optional))
|
|
for _, nestedField := range nested.GetFields() {
|
|
g.writeStructFieldForHelper(sb, nestedField, depth+1)
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
}
|
|
return
|
|
}
|
|
|
|
// Get CUE type for the field type
|
|
fieldType := g.cueTypeForParamType(f.FieldType())
|
|
|
|
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))
|
|
}
|
|
}
|
|
|
|
// writeStructArrayHelper writes a struct-based array helper like mountsArray or volumesArray.
|
|
// This generates the pattern:
|
|
//
|
|
// mountsArray: {
|
|
// pvc: *[for v in parameter.volumeMounts.pvc {...}] | []
|
|
// configMap: *[...] | []
|
|
// ...
|
|
// }
|
|
func (g *CUEGenerator) writeStructArrayHelper(sb *strings.Builder, helper *StructArrayHelper, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
innerIndent := strings.Repeat(g.indent, depth+1)
|
|
fieldIndent := strings.Repeat(g.indent, depth+2)
|
|
nestedIndent := strings.Repeat(g.indent, depth+3)
|
|
|
|
sb.WriteString(fmt.Sprintf("%s%s: {\n", indent, helper.HelperName()))
|
|
|
|
sourceStr := g.valueToCUE(helper.Source())
|
|
|
|
for _, field := range helper.Fields() {
|
|
sb.WriteString(fmt.Sprintf("%s%s: *[\n", innerIndent, field.Name))
|
|
sb.WriteString(fmt.Sprintf("%sfor v in %s.%s {\n", fieldIndent, sourceStr, field.Name))
|
|
sb.WriteString(fmt.Sprintf("%s{\n", fieldIndent))
|
|
|
|
// Write field mappings
|
|
g.writeStructArrayFieldMappings(sb, field.Mappings, nestedIndent)
|
|
|
|
sb.WriteString(fmt.Sprintf("%s}\n", fieldIndent))
|
|
sb.WriteString(fmt.Sprintf("%s},\n", fieldIndent))
|
|
sb.WriteString(fmt.Sprintf("%s] | []\n\n", innerIndent))
|
|
}
|
|
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
}
|
|
|
|
// writeStructArrayFieldMappings writes the field mappings inside a struct array field.
|
|
func (g *CUEGenerator) writeStructArrayFieldMappings(sb *strings.Builder, mappings FieldMap, indent string) {
|
|
for fieldName, fieldVal := range mappings {
|
|
// Handle nested objects like persistentVolumeClaim: claimName: v.claimName
|
|
if strings.Contains(fieldName, ".") {
|
|
parts := strings.Split(fieldName, ".")
|
|
if len(parts) == 2 {
|
|
valStr := g.fieldValueToCUE(fieldVal)
|
|
sb.WriteString(fmt.Sprintf("%s%s: %s: %s\n", indent, parts[0], parts[1], valStr))
|
|
}
|
|
} else if nf, isNested := fieldVal.(*NestedField); isNested {
|
|
// Handle NestedField with its own mapping
|
|
sb.WriteString(fmt.Sprintf("%s%s: {\n", indent, fieldName))
|
|
for nestedName, nestedVal := range nf.mapping {
|
|
if optField, isOptional := nestedVal.(*OptionalField); isOptional {
|
|
sb.WriteString(fmt.Sprintf("%s\tif v.%s != _|_ {\n", indent, optField.field))
|
|
sb.WriteString(fmt.Sprintf("%s\t\t%s: v.%s\n", indent, nestedName, optField.field))
|
|
sb.WriteString(fmt.Sprintf("%s\t}\n", indent))
|
|
} else {
|
|
valStr := g.fieldValueToCUE(nestedVal)
|
|
sb.WriteString(fmt.Sprintf("%s\t%s: %s\n", indent, nestedName, valStr))
|
|
}
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
} else if optField, isOptional := fieldVal.(*OptionalField); isOptional {
|
|
// Handle optional fields with conditional inclusion
|
|
sb.WriteString(fmt.Sprintf("%sif v.%s != _|_ {\n", indent, optField.field))
|
|
sb.WriteString(fmt.Sprintf("%s\t%s: v.%s\n", indent, fieldName, optField.field))
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
} else {
|
|
valStr := g.fieldValueToCUE(fieldVal)
|
|
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, fieldName, valStr))
|
|
}
|
|
}
|
|
}
|
|
|
|
// writeConcatHelper writes a list.Concat helper.
|
|
// This generates: volumesList: list.Concat([volumesArray.pvc, volumesArray.configMap, ...])
|
|
func (g *CUEGenerator) writeConcatHelper(sb *strings.Builder, helper *ConcatHelper, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
|
|
sourceName := helper.Source().HelperName()
|
|
fields := helper.FieldRefs()
|
|
|
|
// Build the field references
|
|
var refs []string
|
|
for _, field := range fields {
|
|
refs = append(refs, fmt.Sprintf("%s.%s", sourceName, field))
|
|
}
|
|
|
|
sb.WriteString(fmt.Sprintf("%s%s: list.Concat([%s])\n",
|
|
indent, helper.HelperName(), strings.Join(refs, ", ")))
|
|
}
|
|
|
|
// writeDedupeHelper writes a deduplication helper.
|
|
// This generates the pattern:
|
|
//
|
|
// deDupVolumesArray: [
|
|
// for val in [
|
|
// for i, vi in volumesList {
|
|
// for j, vj in volumesList if j < i && vi.name == vj.name {
|
|
// _ignore: true
|
|
// }
|
|
// vi
|
|
// },
|
|
// ] if val._ignore == _|_ {
|
|
// val
|
|
// },
|
|
// ]
|
|
func (g *CUEGenerator) writeDedupeHelper(sb *strings.Builder, helper *DedupeHelper, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
|
|
// Get the source name
|
|
var sourceName string
|
|
switch src := helper.Source().(type) {
|
|
case *ConcatHelper:
|
|
sourceName = src.HelperName()
|
|
case *HelperVar:
|
|
sourceName = src.Name()
|
|
case *StructArrayHelper:
|
|
sourceName = src.HelperName()
|
|
default:
|
|
sourceName = g.valueToCUE(helper.Source())
|
|
}
|
|
|
|
keyField := helper.KeyField()
|
|
|
|
sb.WriteString(fmt.Sprintf("%s%s: [\n", indent, helper.HelperName()))
|
|
sb.WriteString(fmt.Sprintf("%s\tfor val in [\n", indent))
|
|
sb.WriteString(fmt.Sprintf("%s\t\tfor i, vi in %s {\n", indent, sourceName))
|
|
sb.WriteString(fmt.Sprintf("%s\t\t\tfor j, vj in %s if j < i && vi.%s == vj.%s {\n",
|
|
indent, sourceName, keyField, keyField))
|
|
sb.WriteString(fmt.Sprintf("%s\t\t\t\t_ignore: true\n", indent))
|
|
sb.WriteString(fmt.Sprintf("%s\t\t\t}\n", indent))
|
|
sb.WriteString(fmt.Sprintf("%s\t\t\tvi\n", indent))
|
|
sb.WriteString(fmt.Sprintf("%s\t\t},\n", indent))
|
|
sb.WriteString(fmt.Sprintf("%s\t] if val._ignore == _|_ {\n", indent))
|
|
sb.WriteString(fmt.Sprintf("%s\t\tval\n", indent))
|
|
sb.WriteString(fmt.Sprintf("%s\t},\n", indent))
|
|
sb.WriteString(fmt.Sprintf("%s]\n", indent))
|
|
}
|
|
|
|
// writeHelper writes a helper definition at the template level.
|
|
func (g *CUEGenerator) writeHelper(sb *strings.Builder, helper *HelperVar, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
|
|
sb.WriteString(fmt.Sprintf("%s%s: ", indent, helper.Name()))
|
|
|
|
// Generate the collection as a CUE expression
|
|
collection := helper.Collection()
|
|
guard := helper.Guard()
|
|
switch col := collection.(type) {
|
|
case *MultiSource:
|
|
g.writeMultiSourceHelper(sb, col, depth)
|
|
case *CollectionOp:
|
|
g.writeCollectionOpHelper(sb, col, depth, guard)
|
|
default:
|
|
sb.WriteString("[]")
|
|
}
|
|
|
|
sb.WriteString("\n")
|
|
}
|
|
|
|
// writeMultiSourceHelper writes a MultiSource as a helper array definition.
|
|
func (g *CUEGenerator) writeMultiSourceHelper(sb *strings.Builder, ms *MultiSource, depth int) {
|
|
sourceStr := g.valueToCUE(ms.Source())
|
|
sources := ms.Sources()
|
|
mapBySource := ms.MapBySourceMappings()
|
|
ops := ms.Operations()
|
|
|
|
// Check for pickIf operations
|
|
var pickIfOps []*pickIfCollectionOp
|
|
var pickFields []string
|
|
for _, op := range ops {
|
|
if pOp, ok := op.(*pickOp); ok {
|
|
pickFields = pOp.fields
|
|
}
|
|
if piOp, ok := op.(*pickIfCollectionOp); ok {
|
|
pickIfOps = append(pickIfOps, piOp)
|
|
}
|
|
}
|
|
|
|
sb.WriteString("[\n")
|
|
|
|
for i, source := range sources {
|
|
if i > 0 {
|
|
sb.WriteString(",\n")
|
|
}
|
|
|
|
innerIndent := strings.Repeat(g.indent, depth+1)
|
|
fieldIndent := strings.Repeat(g.indent, depth+2)
|
|
sb.WriteString(fmt.Sprintf("%sif %s != _|_ && %s.%s != _|_ for v in %s.%s {\n",
|
|
innerIndent, sourceStr, sourceStr, source, sourceStr, source))
|
|
sb.WriteString(fmt.Sprintf("%s{\n", innerIndent))
|
|
|
|
// Check if we have MapBySource mappings for this source
|
|
if mapping, hasMapping := mapBySource[source]; hasMapping {
|
|
g.writeFieldMapAsHelper(sb, mapping, fieldIndent)
|
|
} else if len(pickFields) > 0 {
|
|
// Write pick fields
|
|
for _, field := range pickFields {
|
|
sb.WriteString(fmt.Sprintf("%s%s: v.%s\n", fieldIndent, field, field))
|
|
}
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%sv\n", fieldIndent))
|
|
}
|
|
|
|
// Write conditional fields from pickIf operations
|
|
for _, piOp := range pickIfOps {
|
|
condStr := g.fieldConditionToCUE(piOp.Cond())
|
|
sb.WriteString(fmt.Sprintf("%sif %s {\n", fieldIndent, condStr))
|
|
sb.WriteString(fmt.Sprintf("%s\t%s: v.%s\n", fieldIndent, piOp.Field(), piOp.Field()))
|
|
sb.WriteString(fmt.Sprintf("%s}\n", fieldIndent))
|
|
}
|
|
|
|
sb.WriteString(fmt.Sprintf("%s}\n", innerIndent))
|
|
sb.WriteString(fmt.Sprintf("%s}", innerIndent))
|
|
}
|
|
|
|
sb.WriteString(fmt.Sprintf("\n%s]", strings.Repeat(g.indent, depth)))
|
|
}
|
|
|
|
// writeCollectionOpHelper writes a CollectionOp as a helper array definition.
|
|
// The guard parameter is an optional outer condition that wraps the for loop.
|
|
func (g *CUEGenerator) writeCollectionOpHelper(sb *strings.Builder, col *CollectionOp, depth int, guard Condition) {
|
|
sourceStr := g.valueToCUE(col.Source())
|
|
ops := col.Operations()
|
|
|
|
// Extract filter condition if present
|
|
var filterCondition string
|
|
for _, op := range ops {
|
|
if fOp, ok := op.(*filterOp); ok {
|
|
filterCondition = g.predicateToCUE(fOp.pred)
|
|
}
|
|
}
|
|
|
|
// Build the guard prefix (if guard != _|_ for v in source)
|
|
var guardPrefix string
|
|
if guard != nil {
|
|
guardPrefix = "if " + g.conditionToCUE(guard) + " "
|
|
}
|
|
|
|
// Check for wrap operation (e.g., imagePullSecrets)
|
|
for _, op := range ops {
|
|
if wOp, ok := op.(*wrapOp); ok {
|
|
if filterCondition != "" {
|
|
sb.WriteString(fmt.Sprintf("[%sfor v in %s if %s { %s: v }]", guardPrefix, sourceStr, filterCondition, wOp.key))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("[%sfor v in %s { %s: v }]", guardPrefix, sourceStr, wOp.key))
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
// Check for map operations
|
|
for _, op := range ops {
|
|
if mOp, ok := op.(*mapOp); ok {
|
|
innerIndent := strings.Repeat(g.indent, depth+1)
|
|
fieldIndent := strings.Repeat(g.indent, depth+2)
|
|
|
|
// Include guard and filter condition in the for loop if present
|
|
// No extra braces - the for loop body directly contains the struct fields
|
|
if filterCondition != "" {
|
|
sb.WriteString(fmt.Sprintf("[\n%s%sfor v in %s if %s {\n", innerIndent, guardPrefix, sourceStr, filterCondition))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("[\n%s%sfor v in %s {\n", innerIndent, guardPrefix, sourceStr))
|
|
}
|
|
|
|
for fieldName, fieldVal := range mOp.mappings {
|
|
valStr := g.fieldValueToCUE(fieldVal)
|
|
sb.WriteString(fmt.Sprintf("%s%s: %s\n", fieldIndent, fieldName, valStr))
|
|
}
|
|
|
|
sb.WriteString(fmt.Sprintf("%s},\n%s]", innerIndent, strings.Repeat(g.indent, depth)))
|
|
return
|
|
}
|
|
}
|
|
|
|
// Check for deduplication
|
|
for _, op := range ops {
|
|
if dedup, ok := op.(*dedupeOp); ok {
|
|
// For dedupe, use the elegant CUE pattern with _ignore marker
|
|
// This pattern checks if any earlier item has the same key
|
|
if helperRef, ok := col.Source().(*HelperVar); ok {
|
|
keyField := dedup.keyField
|
|
sb.WriteString(fmt.Sprintf(`[
|
|
for val in [
|
|
for i, vi in %s {
|
|
for j, vj in %s if j < i && vi.%s == vj.%s {
|
|
_ignore: true
|
|
}
|
|
vi
|
|
},
|
|
] if val._ignore == _|_ {
|
|
val
|
|
},
|
|
]`, helperRef.Name(), helperRef.Name(), keyField, keyField))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Default: simple list comprehension
|
|
sb.WriteString(fmt.Sprintf("[for v in %s { v }]", sourceStr))
|
|
}
|
|
|
|
// writeFieldMapAsHelper writes a FieldMap as CUE fields.
|
|
func (g *CUEGenerator) writeFieldMapAsHelper(sb *strings.Builder, mapping FieldMap, indent string) {
|
|
for fieldName, fieldVal := range mapping {
|
|
// Handle nested paths like "persistentVolumeClaim.claimName"
|
|
if strings.Contains(fieldName, ".") {
|
|
g.writeNestedFieldPath(sb, fieldName, fieldVal, indent)
|
|
} else {
|
|
valStr := g.fieldValueToCUE(fieldVal)
|
|
// Check if this is an optional field
|
|
if _, isOptional := fieldVal.(*OptionalField); isOptional {
|
|
sb.WriteString(fmt.Sprintf("%sif v.%s != _|_ {\n", indent, fieldVal.(*OptionalField).field))
|
|
sb.WriteString(fmt.Sprintf("%s\t%s: v.%s\n", indent, fieldName, fieldVal.(*OptionalField).field))
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, fieldName, valStr))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// writeNestedFieldPath writes a nested field path like "persistentVolumeClaim.claimName".
|
|
func (g *CUEGenerator) writeNestedFieldPath(sb *strings.Builder, fieldPath string, fieldVal FieldValue, indent string) {
|
|
parts := strings.Split(fieldPath, ".")
|
|
if len(parts) < 2 {
|
|
valStr := g.fieldValueToCUE(fieldVal)
|
|
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, fieldPath, valStr))
|
|
return
|
|
}
|
|
|
|
// Build nested structure: persistentVolumeClaim: claimName: v.claimName
|
|
parent := parts[0]
|
|
child := parts[1]
|
|
valStr := g.fieldValueToCUE(fieldVal)
|
|
sb.WriteString(fmt.Sprintf("%s%s: %s: %s\n", indent, parent, child, valStr))
|
|
}
|
|
|
|
// fieldConditionToCUE converts a field-level condition to CUE.
|
|
func (g *CUEGenerator) fieldConditionToCUE(cond Condition) string {
|
|
switch c := cond.(type) {
|
|
case *IsSetCondition:
|
|
// For field-level conditions, use v.field instead of parameter.field
|
|
return fmt.Sprintf("v.%s != _|_", c.ParamName())
|
|
default:
|
|
return g.conditionToCUE(cond)
|
|
}
|
|
}
|
|
|
|
// writeResourceOutput writes a resource as a CUE output block.
|
|
func (g *CUEGenerator) writeResourceOutput(sb *strings.Builder, name string, res *Resource, cond Condition, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
|
|
// Handle conditional output (wrap in if block)
|
|
if cond != nil {
|
|
condStr := g.conditionToCUE(cond)
|
|
sb.WriteString(fmt.Sprintf("%sif %s {\n", indent, condStr))
|
|
depth++
|
|
indent = strings.Repeat(g.indent, depth)
|
|
}
|
|
|
|
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
|
|
if res.HasVersionConditionals() {
|
|
// Write each version conditional as an if block
|
|
for _, vc := range res.VersionConditionals() {
|
|
condStr := g.conditionToCUE(vc.Condition)
|
|
sb.WriteString(fmt.Sprintf("%sif %s {\n", innerIndent, condStr))
|
|
sb.WriteString(fmt.Sprintf("%s\tapiVersion: %q\n", innerIndent, vc.ApiVersion))
|
|
sb.WriteString(fmt.Sprintf("%s}\n", innerIndent))
|
|
}
|
|
} else {
|
|
// Static apiVersion
|
|
sb.WriteString(fmt.Sprintf("%sapiVersion: %q\n", innerIndent, res.APIVersion()))
|
|
}
|
|
|
|
// Write kind
|
|
sb.WriteString(fmt.Sprintf("%skind: %q\n", innerIndent, res.Kind()))
|
|
|
|
// Build a tree structure from the operations
|
|
tree := g.buildFieldTree(res.Ops())
|
|
|
|
// Write the tree as CUE
|
|
g.writeFieldTree(sb, tree, depth+1)
|
|
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
|
|
// Close conditional block
|
|
if cond != nil {
|
|
indent = strings.Repeat(g.indent, depth-1)
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
}
|
|
}
|
|
|
|
// fieldNode represents a node in the field tree being built.
|
|
type fieldNode struct {
|
|
value Value // Direct value (if leaf)
|
|
cond Condition // Condition for this field
|
|
children map[string]*fieldNode
|
|
childOrder []string // Track insertion order
|
|
isArray bool
|
|
arrayIndex int
|
|
spreads []spreadEntry // Spread operations at this node level
|
|
forEach *ForEachOp // ForEach operation (for trait patches)
|
|
patchKey *PatchKeyOp // PatchKey operation (for array patches with merge key)
|
|
spreadAll *SpreadAllOp // SpreadAll operation (for array constraint patches)
|
|
patchStrategy string // e.g. "retainKeys" → generates // +patchStrategy=retainKeys
|
|
}
|
|
|
|
// spreadEntry represents a conditional spread operation.
|
|
type spreadEntry struct {
|
|
cond Condition
|
|
value Value
|
|
}
|
|
|
|
func newFieldNode() *fieldNode {
|
|
return &fieldNode{
|
|
children: make(map[string]*fieldNode),
|
|
}
|
|
}
|
|
|
|
// buildFieldTree builds a tree structure from resource operations.
|
|
func (g *CUEGenerator) buildFieldTree(ops []ResourceOp) *fieldNode {
|
|
root := newFieldNode()
|
|
|
|
for _, op := range ops {
|
|
switch o := op.(type) {
|
|
case *SetOp:
|
|
g.insertIntoTree(root, o.Path(), o.Value(), nil)
|
|
case *SetIfOp:
|
|
g.insertIntoTree(root, o.Path(), o.Value(), o.Cond())
|
|
case *SpreadIfOp:
|
|
// SpreadIfOp adds a spread entry to the target node
|
|
g.insertSpreadIntoTree(root, o.Path(), o.Value(), o.Cond())
|
|
case *ForEachOp:
|
|
// ForEachOp creates a for-each iteration at the target path
|
|
g.insertForEachIntoTree(root, o, nil)
|
|
case *PatchKeyOp:
|
|
// PatchKeyOp creates an array patch with merge key annotation
|
|
g.insertPatchKeyIntoTree(root, o, nil)
|
|
case *SpreadAllOp:
|
|
// SpreadAllOp constrains all array elements
|
|
g.insertSpreadAllIntoTree(root, o, nil)
|
|
case *PatchStrategyAnnotationOp:
|
|
g.insertAnnotationIntoTree(root, o.Path(), o.Strategy())
|
|
case *IfBlock:
|
|
// For if blocks, process inner ops with the block's condition
|
|
for _, innerOp := range o.Ops() {
|
|
switch inner := innerOp.(type) {
|
|
case *SetOp:
|
|
g.insertIntoTree(root, inner.Path(), inner.Value(), o.Cond())
|
|
case *SetIfOp:
|
|
// Combine conditions
|
|
combinedCond := &AndCondition{left: o.Cond(), right: inner.Cond()}
|
|
g.insertIntoTree(root, inner.Path(), inner.Value(), combinedCond)
|
|
case *SpreadIfOp:
|
|
// Combine conditions for spread
|
|
combinedCond := &AndCondition{left: o.Cond(), right: inner.Cond()}
|
|
g.insertSpreadIntoTree(root, inner.Path(), inner.Value(), combinedCond)
|
|
case *ForEachOp:
|
|
// ForEach inside an if block - pass the block's condition
|
|
g.insertForEachIntoTree(root, inner, o.Cond())
|
|
case *PatchKeyOp:
|
|
// PatchKey inside an if block - pass the block's condition
|
|
g.insertPatchKeyIntoTree(root, inner, o.Cond())
|
|
case *SpreadAllOp:
|
|
// SpreadAll inside an if block - pass the block's condition
|
|
g.insertSpreadAllIntoTree(root, inner, o.Cond())
|
|
case *PatchStrategyAnnotationOp:
|
|
g.insertAnnotationIntoTree(root, inner.Path(), inner.Strategy())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return root
|
|
}
|
|
|
|
// insertAnnotationIntoTree navigates to a node by path and sets its patchStrategy annotation.
|
|
func (g *CUEGenerator) insertAnnotationIntoTree(root *fieldNode, path string, strategy string) {
|
|
parts := splitPath(path)
|
|
current := root
|
|
for _, part := range parts {
|
|
if _, exists := current.children[part]; !exists {
|
|
current.children[part] = newFieldNode()
|
|
current.childOrder = append(current.childOrder, part)
|
|
}
|
|
current = current.children[part]
|
|
}
|
|
current.patchStrategy = strategy
|
|
}
|
|
|
|
// insertIntoTree inserts a value at a path into the field tree.
|
|
func (g *CUEGenerator) insertIntoTree(root *fieldNode, path string, value Value, cond Condition) {
|
|
parts := splitPath(path)
|
|
current := root
|
|
|
|
for i, part := range parts {
|
|
name, key, index := parseBracketAccess(part)
|
|
|
|
// Get or create node for the field name
|
|
if _, exists := current.children[name]; !exists {
|
|
current.children[name] = newFieldNode()
|
|
current.childOrder = append(current.childOrder, name)
|
|
}
|
|
node := current.children[name]
|
|
|
|
// Handle array access
|
|
switch {
|
|
case index >= 0:
|
|
node.isArray = true
|
|
idxKey := fmt.Sprintf("[%d]", index)
|
|
if _, exists := node.children[idxKey]; !exists {
|
|
node.children[idxKey] = newFieldNode()
|
|
node.children[idxKey].arrayIndex = index
|
|
node.childOrder = append(node.childOrder, idxKey)
|
|
}
|
|
current = node.children[idxKey]
|
|
case key != "":
|
|
// Map key access (e.g., labels[app.oam.dev/name])
|
|
// Create a special child for the key
|
|
keyNode := fmt.Sprintf("[%s]", key)
|
|
if _, exists := node.children[keyNode]; !exists {
|
|
node.children[keyNode] = newFieldNode()
|
|
node.childOrder = append(node.childOrder, keyNode)
|
|
}
|
|
current = node.children[keyNode]
|
|
default:
|
|
current = node
|
|
}
|
|
|
|
// If this is the last part, set the value
|
|
if i == len(parts)-1 {
|
|
current.value = value
|
|
current.cond = cond
|
|
}
|
|
}
|
|
}
|
|
|
|
// insertSpreadIntoTree inserts a spread operation at a path into the field tree.
|
|
// Unlike insertIntoTree which sets a value, this adds a spread entry that will
|
|
// be written inside the struct block at the given path.
|
|
func (g *CUEGenerator) insertSpreadIntoTree(root *fieldNode, path string, value Value, cond Condition) {
|
|
parts := splitPath(path)
|
|
current := root
|
|
|
|
for _, part := range parts {
|
|
name, key, index := parseBracketAccess(part)
|
|
|
|
// Get or create node for the field name
|
|
if _, exists := current.children[name]; !exists {
|
|
current.children[name] = newFieldNode()
|
|
current.childOrder = append(current.childOrder, name)
|
|
}
|
|
node := current.children[name]
|
|
|
|
// Handle array access
|
|
switch {
|
|
case index >= 0:
|
|
node.isArray = true
|
|
idxKey := fmt.Sprintf("[%d]", index)
|
|
if _, exists := node.children[idxKey]; !exists {
|
|
node.children[idxKey] = newFieldNode()
|
|
node.children[idxKey].arrayIndex = index
|
|
node.childOrder = append(node.childOrder, idxKey)
|
|
}
|
|
current = node.children[idxKey]
|
|
case key != "":
|
|
// Map key access
|
|
keyNode := fmt.Sprintf("[%s]", key)
|
|
if _, exists := node.children[keyNode]; !exists {
|
|
node.children[keyNode] = newFieldNode()
|
|
node.childOrder = append(node.childOrder, keyNode)
|
|
}
|
|
current = node.children[keyNode]
|
|
default:
|
|
current = node
|
|
}
|
|
}
|
|
|
|
// Add spread entry to the final node
|
|
current.spreads = append(current.spreads, spreadEntry{cond: cond, value: value})
|
|
}
|
|
|
|
// insertForEachIntoTree inserts a ForEach operation at the given path.
|
|
// The cond parameter is used when the ForEach is inside an IfBlock.
|
|
func (g *CUEGenerator) insertForEachIntoTree(root *fieldNode, op *ForEachOp, cond Condition) {
|
|
parts := splitPath(op.Path())
|
|
current := root
|
|
|
|
for _, part := range parts {
|
|
name, key, index := parseBracketAccess(part)
|
|
|
|
// Get or create node for the field name
|
|
if _, exists := current.children[name]; !exists {
|
|
current.children[name] = newFieldNode()
|
|
current.childOrder = append(current.childOrder, name)
|
|
}
|
|
node := current.children[name]
|
|
|
|
// Handle array access
|
|
switch {
|
|
case index >= 0:
|
|
node.isArray = true
|
|
idxKey := fmt.Sprintf("[%d]", index)
|
|
if _, exists := node.children[idxKey]; !exists {
|
|
node.children[idxKey] = newFieldNode()
|
|
node.children[idxKey].arrayIndex = index
|
|
node.childOrder = append(node.childOrder, idxKey)
|
|
}
|
|
current = node.children[idxKey]
|
|
case key != "":
|
|
// Map key access
|
|
keyNode := fmt.Sprintf("[%s]", key)
|
|
if _, exists := node.children[keyNode]; !exists {
|
|
node.children[keyNode] = newFieldNode()
|
|
node.childOrder = append(node.childOrder, keyNode)
|
|
}
|
|
current = node.children[keyNode]
|
|
default:
|
|
current = node
|
|
}
|
|
}
|
|
|
|
// Set forEach on the final node with its condition
|
|
current.forEach = op
|
|
current.cond = cond
|
|
}
|
|
|
|
// insertPatchKeyIntoTree inserts a PatchKeyOp into the field tree.
|
|
// This navigates to the target path and sets the patchKey field.
|
|
// The cond parameter is used when the PatchKey is inside an IfBlock.
|
|
func (g *CUEGenerator) insertPatchKeyIntoTree(root *fieldNode, op *PatchKeyOp, cond Condition) {
|
|
parts := splitPath(op.Path())
|
|
current := root
|
|
|
|
for _, part := range parts {
|
|
name, key, index := parseBracketAccess(part)
|
|
|
|
// Get or create node for the field name
|
|
if _, exists := current.children[name]; !exists {
|
|
current.children[name] = newFieldNode()
|
|
current.childOrder = append(current.childOrder, name)
|
|
}
|
|
node := current.children[name]
|
|
|
|
// Handle array access
|
|
switch {
|
|
case index >= 0:
|
|
node.isArray = true
|
|
idxKey := fmt.Sprintf("[%d]", index)
|
|
if _, exists := node.children[idxKey]; !exists {
|
|
node.children[idxKey] = newFieldNode()
|
|
node.children[idxKey].arrayIndex = index
|
|
node.childOrder = append(node.childOrder, idxKey)
|
|
}
|
|
current = node.children[idxKey]
|
|
case key != "":
|
|
// Map key access
|
|
keyNode := fmt.Sprintf("[%s]", key)
|
|
if _, exists := node.children[keyNode]; !exists {
|
|
node.children[keyNode] = newFieldNode()
|
|
node.childOrder = append(node.childOrder, keyNode)
|
|
}
|
|
current = node.children[keyNode]
|
|
default:
|
|
current = node
|
|
}
|
|
}
|
|
|
|
// Set patchKey on the final node with its condition
|
|
current.patchKey = op
|
|
current.cond = cond
|
|
}
|
|
|
|
// insertSpreadAllIntoTree inserts a SpreadAllOp into the field tree.
|
|
// This navigates to the target path and sets the spreadAll field.
|
|
func (g *CUEGenerator) insertSpreadAllIntoTree(root *fieldNode, op *SpreadAllOp, cond Condition) {
|
|
parts := splitPath(op.Path())
|
|
current := root
|
|
|
|
for _, part := range parts {
|
|
name, key, index := parseBracketAccess(part)
|
|
|
|
if _, exists := current.children[name]; !exists {
|
|
current.children[name] = newFieldNode()
|
|
current.childOrder = append(current.childOrder, name)
|
|
}
|
|
node := current.children[name]
|
|
|
|
switch {
|
|
case index >= 0:
|
|
node.isArray = true
|
|
idxKey := fmt.Sprintf("[%d]", index)
|
|
if _, exists := node.children[idxKey]; !exists {
|
|
node.children[idxKey] = newFieldNode()
|
|
node.children[idxKey].arrayIndex = index
|
|
node.childOrder = append(node.childOrder, idxKey)
|
|
}
|
|
current = node.children[idxKey]
|
|
case key != "":
|
|
keyNode := fmt.Sprintf("[%s]", key)
|
|
if _, exists := node.children[keyNode]; !exists {
|
|
node.children[keyNode] = newFieldNode()
|
|
node.childOrder = append(node.childOrder, keyNode)
|
|
}
|
|
current = node.children[keyNode]
|
|
default:
|
|
current = node
|
|
}
|
|
}
|
|
|
|
current.spreadAll = op
|
|
current.cond = cond
|
|
}
|
|
|
|
// writeFieldTree writes the field tree as CUE syntax.
|
|
func (g *CUEGenerator) writeFieldTree(sb *strings.Builder, node *fieldNode, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
|
|
// Lift child conditions to the parent field when all children share the same
|
|
// condition and the parent has no own value/spread/foreach/patchKey. This avoids
|
|
// emitting empty parent structs like `foo: { if cond { bar: ... } }`.
|
|
g.liftChildConditions(node)
|
|
|
|
// Write spread entries FIRST (user labels spread before OAM labels)
|
|
// This matches the KubeVela convention of spreading user-provided values
|
|
// before adding fixed OAM labels, so user values can be overridden if needed.
|
|
for _, spread := range node.spreads {
|
|
condStr := g.conditionToCUE(spread.cond)
|
|
valStr := g.valueToCUE(spread.value)
|
|
sb.WriteString(fmt.Sprintf("%sif %s {\n", indent, condStr))
|
|
sb.WriteString(fmt.Sprintf("%s\t%s\n", indent, valStr))
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
}
|
|
|
|
// Group fields by their condition for cleaner output
|
|
unconditional := make([]string, 0)
|
|
conditional := make(map[string][]string) // condition string -> field names
|
|
|
|
for _, name := range node.childOrder {
|
|
child := node.children[name]
|
|
if child.cond != nil {
|
|
condStr := g.conditionToCUE(child.cond)
|
|
conditional[condStr] = append(conditional[condStr], name)
|
|
} else {
|
|
unconditional = append(unconditional, name)
|
|
}
|
|
}
|
|
|
|
// Write unconditional fields
|
|
for _, name := range unconditional {
|
|
child := node.children[name]
|
|
g.writeFieldNode(sb, name, child, depth)
|
|
}
|
|
|
|
// Write conditional fields grouped by condition
|
|
for condStr, fieldNames := range conditional {
|
|
sb.WriteString(fmt.Sprintf("%sif %s {\n", indent, condStr))
|
|
for _, name := range fieldNames {
|
|
child := node.children[name]
|
|
// Clear condition since we're inside the if block
|
|
childCopy := *child
|
|
childCopy.cond = nil
|
|
g.writeFieldNode(sb, name, &childCopy, depth+1)
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
}
|
|
}
|
|
|
|
// liftChildConditions promotes a shared child condition to the parent node.
|
|
// It recursively processes the tree so inner nodes are normalized before parent rendering.
|
|
func (g *CUEGenerator) liftChildConditions(node *fieldNode) {
|
|
for _, name := range node.childOrder {
|
|
child := node.children[name]
|
|
if child == nil {
|
|
continue
|
|
}
|
|
// Recurse first so deeper nodes are normalized
|
|
g.liftChildConditions(child)
|
|
|
|
if child.cond != nil {
|
|
continue
|
|
}
|
|
if child.value != nil || child.isArray || len(child.spreads) > 0 || child.forEach != nil || child.patchKey != nil {
|
|
continue
|
|
}
|
|
if len(child.children) == 0 {
|
|
continue
|
|
}
|
|
|
|
var sharedCond Condition
|
|
var sharedCondStr string
|
|
canLift := true
|
|
for _, grandName := range child.childOrder {
|
|
grand := child.children[grandName]
|
|
if grand == nil || grand.cond == nil {
|
|
canLift = false
|
|
break
|
|
}
|
|
condStr := g.conditionToCUE(grand.cond)
|
|
if sharedCondStr == "" {
|
|
sharedCondStr = condStr
|
|
sharedCond = grand.cond
|
|
} else if sharedCondStr != condStr {
|
|
canLift = false
|
|
break
|
|
}
|
|
}
|
|
if canLift && sharedCond != nil {
|
|
child.cond = sharedCond
|
|
for _, grandName := range child.childOrder {
|
|
grand := child.children[grandName]
|
|
if grand != nil {
|
|
grand.cond = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// writeFieldNode writes a single field node as CUE.
|
|
func (g *CUEGenerator) writeFieldNode(sb *strings.Builder, name string, node *fieldNode, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
|
|
// Handle bracket notation in name (like [app.oam.dev/name])
|
|
if strings.HasPrefix(name, "[") && !strings.HasPrefix(name, "[0]") {
|
|
// This is a map key access - extract the key
|
|
key := strings.Trim(name, "[]")
|
|
if node.value != nil {
|
|
valStr := g.valueToCUE(node.value)
|
|
sb.WriteString(fmt.Sprintf("%s%q: %s\n", indent, key, valStr))
|
|
} else if len(node.children) > 0 {
|
|
sb.WriteString(fmt.Sprintf("%s%q: {\n", indent, key))
|
|
g.writeFieldTree(sb, node, depth+1)
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
}
|
|
return
|
|
}
|
|
|
|
// Handle array notation
|
|
if node.isArray {
|
|
sb.WriteString(fmt.Sprintf("%s%s: [{\n", indent, name))
|
|
// Write the first array element (index 0)
|
|
if child, exists := node.children["[0]"]; exists {
|
|
g.writeFieldTree(sb, child, depth+1)
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s}]\n", indent))
|
|
return
|
|
}
|
|
|
|
// If all children share the same condition and there are no unconditional parts,
|
|
// lift the condition to avoid emitting empty parent structs.
|
|
if node.value == nil && len(node.children) > 0 && len(node.spreads) == 0 && node.forEach == nil && node.patchKey == nil {
|
|
condStr := ""
|
|
canLift := true
|
|
for _, childName := range node.childOrder {
|
|
child := node.children[childName]
|
|
if child.cond == nil {
|
|
canLift = false
|
|
break
|
|
}
|
|
childCondStr := g.conditionToCUE(child.cond)
|
|
if condStr == "" {
|
|
condStr = childCondStr
|
|
} else if condStr != childCondStr {
|
|
canLift = false
|
|
break
|
|
}
|
|
}
|
|
if canLift && condStr != "" {
|
|
// Clone node with cleared child conditions for rendering.
|
|
clone := &fieldNode{
|
|
children: make(map[string]*fieldNode, len(node.children)),
|
|
childOrder: append([]string(nil), node.childOrder...),
|
|
}
|
|
for childName, child := range node.children {
|
|
childCopy := *child
|
|
childCopy.cond = nil
|
|
clone.children[childName] = &childCopy
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%sif %s {\n", indent, condStr))
|
|
sb.WriteString(fmt.Sprintf("%s\t%s: {\n", indent, name))
|
|
g.writeFieldTree(sb, clone, depth+2)
|
|
sb.WriteString(fmt.Sprintf("%s\t}\n", indent))
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
return
|
|
}
|
|
}
|
|
|
|
// Regular field
|
|
if node.value != nil && len(node.children) == 0 {
|
|
// Leaf node with value
|
|
valStr := g.valueToCUE(node.value)
|
|
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, name, valStr))
|
|
} else if len(node.children) > 0 {
|
|
// Node with children - write as nested struct
|
|
sb.WriteString(fmt.Sprintf("%s%s: {\n", indent, name))
|
|
g.writeFieldTree(sb, node, depth+1)
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
}
|
|
}
|
|
|
|
// valueToCUE converts a Value to CUE syntax.
|
|
func (g *CUEGenerator) valueToCUE(v Value) string {
|
|
switch val := v.(type) {
|
|
case *Literal:
|
|
return formatCUEValue(val.Val())
|
|
case *ContextRef:
|
|
return val.Path()
|
|
case *ContextOutputRef:
|
|
return val.Path()
|
|
case *Ref:
|
|
return val.Path()
|
|
case *HelperVar:
|
|
// Return reference to the helper by name
|
|
return 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"
|
|
case *ParamPathRef:
|
|
// Parameter path reference like "podAffinity.required" => "parameter.podAffinity.required"
|
|
return "parameter." + val.Path()
|
|
case *CollectionOp:
|
|
return g.collectionOpToCUE(val)
|
|
case *MultiSource:
|
|
return g.multiSourceToCUE(val)
|
|
case *ConcatExprValue:
|
|
return g.concatExprToCUE(val)
|
|
case *CUEFunc:
|
|
return g.cueFuncToCUE(val)
|
|
case *ArrayElement:
|
|
return g.arrayElementToCUE(val)
|
|
case *StructArrayHelper:
|
|
// Return reference to the helper by name
|
|
return val.HelperName()
|
|
case *ConcatHelper:
|
|
// Return reference to the helper by name
|
|
return val.HelperName()
|
|
case *DedupeHelper:
|
|
// Return reference to the helper by name
|
|
return val.HelperName()
|
|
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)
|
|
case *ParamArithExpr:
|
|
// Arithmetic expression on a parameter: parameter.name op value
|
|
return fmt.Sprintf("parameter.%s %s %s", val.ParamName(), val.Op(), formatCUEValue(val.ArithValue()))
|
|
case *ParamConcatExpr:
|
|
// String concatenation on a parameter
|
|
if val.Prefix() != "" {
|
|
return fmt.Sprintf("%s + parameter.%s", formatCUEValue(val.Prefix()), val.ParamName())
|
|
}
|
|
return fmt.Sprintf("parameter.%s + %s", val.ParamName(), formatCUEValue(val.Suffix()))
|
|
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()
|
|
case *ForEachMapOp:
|
|
return g.forEachMapOpToCUE(val)
|
|
default:
|
|
// Try to get name from Param interface
|
|
if p, ok := v.(Param); ok {
|
|
return "parameter." + p.Name()
|
|
}
|
|
return "_"
|
|
}
|
|
}
|
|
|
|
// forEachMapOpToCUE converts a ForEachMapOp to CUE map comprehension syntax.
|
|
// Generates: {for k, v in source { (keyExpr): valExpr }}.
|
|
func (g *CUEGenerator) forEachMapOpToCUE(op *ForEachMapOp) string {
|
|
keyVar := op.KeyVar()
|
|
if keyVar == "" {
|
|
keyVar = "k"
|
|
}
|
|
|
|
valVar := op.ValVar()
|
|
if valVar == "" {
|
|
valVar = "v"
|
|
}
|
|
|
|
keyExpr := op.KeyExpr()
|
|
if keyExpr == "" {
|
|
keyExpr = keyVar
|
|
}
|
|
|
|
valExpr := op.ValExpr()
|
|
if valExpr == "" {
|
|
valExpr = valVar
|
|
}
|
|
|
|
return fmt.Sprintf("{for %s, %s in %s { (%s): %s }}", keyVar, valVar, op.Source(), keyExpr, valExpr)
|
|
}
|
|
|
|
// cueFuncToCUE converts a CUE function call to CUE syntax.
|
|
func (g *CUEGenerator) cueFuncToCUE(fn *CUEFunc) string {
|
|
args := make([]string, len(fn.Args()))
|
|
for i, arg := range fn.Args() {
|
|
args[i] = g.valueToCUE(arg)
|
|
}
|
|
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 {
|
|
return g.arrayElementToCUEWithDepth(elem, 1)
|
|
}
|
|
|
|
// arrayElementToCUEWithDepth converts an ArrayElement to CUE syntax with proper indentation.
|
|
// The depth parameter indicates the nesting level for proper indentation.
|
|
func (g *CUEGenerator) arrayElementToCUEWithDepth(elem *ArrayElement, depth int) string {
|
|
var sb strings.Builder
|
|
indent := strings.Repeat(g.indent, depth)
|
|
innerIndent := strings.Repeat(g.indent, depth+1)
|
|
|
|
sb.WriteString("{\n")
|
|
for key, val := range elem.Fields() {
|
|
valStr := g.valueToCUE(val)
|
|
sb.WriteString(fmt.Sprintf("%s%s: %s\n", innerIndent, key, valStr))
|
|
}
|
|
// Write conditional operations
|
|
for _, op := range elem.Ops() {
|
|
if setIf, ok := op.(*SetIfOp); ok {
|
|
condStr := g.conditionToCUE(setIf.Cond())
|
|
valStr := g.valueToCUE(setIf.Value())
|
|
// Convert dot-separated path to CUE shorthand syntax: "a.b.c" -> "a: b: c"
|
|
cuePath := strings.ReplaceAll(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))
|
|
}
|
|
}
|
|
// 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())
|
|
ops := col.Operations()
|
|
|
|
// Check for wrap operation (e.g., imagePullSecrets wrapping string to {name: string})
|
|
for _, op := range ops {
|
|
if wOp, ok := op.(*wrapOp); ok {
|
|
return fmt.Sprintf("[for v in %s { %s: v }]", sourceStr, wOp.key)
|
|
}
|
|
}
|
|
|
|
// Build filter condition if present
|
|
filterCondition := ""
|
|
for _, op := range ops {
|
|
if fOp, ok := op.(*filterOp); ok {
|
|
filterCondition = g.predicateToCUE(fOp.pred)
|
|
}
|
|
}
|
|
|
|
// 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("[")
|
|
|
|
// 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)
|
|
}
|
|
|
|
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 {
|
|
if optField, isOptional := fieldVal.(*OptionalField); isOptional {
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\tif v.%s != _|_ {\n", optField.field))
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\t\t%s: v.%s\n", fieldName, optField.field))
|
|
sb.WriteString("\t\t\t\t\t}\n")
|
|
} else {
|
|
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}]")
|
|
}
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
// predicateToCUE converts a Predicate to CUE filter condition.
|
|
func (g *CUEGenerator) predicateToCUE(pred Predicate) string {
|
|
switch p := pred.(type) {
|
|
case FieldEq:
|
|
// Generate: v.field == value
|
|
return fmt.Sprintf("v.%s == %s", p.field, formatCUEValue(p.value))
|
|
case FieldIsSet:
|
|
// Generate: v.field != _|_
|
|
return fmt.Sprintf("v.%s != _|_", p.field)
|
|
default:
|
|
return cueBoolTrue
|
|
}
|
|
}
|
|
|
|
// listComprehensionToCUE generates CUE for a ListComprehension.
|
|
// This generates: [for v in source { ... if v.field != _|_ { field: v.field } }]
|
|
func (g *CUEGenerator) listComprehensionToCUE(lc *ListComprehension) string {
|
|
sourceStr := g.valueToCUE(lc.Source())
|
|
mappings := lc.Mappings()
|
|
conditionalFields := lc.ConditionalFields()
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString("[\n")
|
|
sb.WriteString("\t\tfor v in ")
|
|
sb.WriteString(sourceStr)
|
|
sb.WriteString(" {\n")
|
|
|
|
// Write direct field mappings
|
|
for fieldName, fieldVal := range mappings {
|
|
// Check if this field is conditional
|
|
isConditional := false
|
|
for _, cf := range conditionalFields {
|
|
if cf == fieldName {
|
|
isConditional = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// Check if the field value is an OptionalField
|
|
if optField, ok := fieldVal.(*OptionalField); ok {
|
|
// Generate conditional: if v.field != _|_ { field: v.field }
|
|
sb.WriteString(fmt.Sprintf("\t\t\tif v.%s != _|_ {\n", optField.field))
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t%s: v.%s\n", fieldName, optField.field))
|
|
sb.WriteString("\t\t\t}\n")
|
|
} else if isConditional {
|
|
// Field is in conditionalFields list
|
|
sourceField := fieldName
|
|
if fr, ok := fieldVal.(FieldRef); ok {
|
|
sourceField = string(fr)
|
|
}
|
|
sb.WriteString(fmt.Sprintf("\t\t\tif v.%s != _|_ {\n", sourceField))
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t%s: v.%s\n", fieldName, sourceField))
|
|
sb.WriteString("\t\t\t}\n")
|
|
} else {
|
|
// Direct field reference
|
|
sourceField := fieldName
|
|
if fr, ok := fieldVal.(FieldRef); ok {
|
|
sourceField = string(fr)
|
|
}
|
|
sb.WriteString(fmt.Sprintf("\t\t\t%s: v.%s\n", fieldName, sourceField))
|
|
}
|
|
}
|
|
|
|
sb.WriteString("\t\t}\n")
|
|
sb.WriteString("\t]")
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
// listPredicateToCUE converts a ListPredicate to CUE filter condition.
|
|
//
|
|
//lint:ignore U1000 planned for future use
|
|
func (g *CUEGenerator) listPredicateToCUE(pred ListPredicate) string {
|
|
switch p := pred.(type) {
|
|
case *ListFieldExistsPredicate:
|
|
return fmt.Sprintf("v.%s != _|_", p.GetField())
|
|
default:
|
|
return cueBoolTrue
|
|
}
|
|
}
|
|
|
|
// multiSourceToCUE generates CUE for a multi-source collection.
|
|
// This generates the complex volumeMounts-style comprehension pattern.
|
|
func (g *CUEGenerator) multiSourceToCUE(ms *MultiSource) string {
|
|
sourceStr := g.valueToCUE(ms.Source())
|
|
sources := ms.Sources()
|
|
mapBySource := ms.MapBySourceMappings()
|
|
|
|
// Check if we have Pick operations (for volumeMounts -> container mounts)
|
|
for _, op := range ms.Operations() {
|
|
if pOp, ok := op.(*pickOp); ok {
|
|
return g.generatePickMultiSource(sourceStr, sources, pOp.fields)
|
|
}
|
|
}
|
|
|
|
// Check if we have MapBySource (for volumeMounts -> pod volumes)
|
|
if len(mapBySource) > 0 {
|
|
return g.generateMapBySourceCUE(sourceStr, sources, mapBySource)
|
|
}
|
|
|
|
// Fallback: simple flattening
|
|
var sb strings.Builder
|
|
sb.WriteString("[\n")
|
|
for i, source := range sources {
|
|
if i > 0 {
|
|
sb.WriteString(",\n")
|
|
}
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\tif %s.%s != _|_ for v in %s.%s { v }", sourceStr, source, sourceStr, source))
|
|
}
|
|
sb.WriteString("\n\t\t\t\t]")
|
|
return sb.String()
|
|
}
|
|
|
|
// generatePickMultiSource generates CUE for picking fields from multiple sources.
|
|
func (g *CUEGenerator) generatePickMultiSource(sourceStr string, sources []string, fields []string) string {
|
|
var sb strings.Builder
|
|
sb.WriteString("[\n")
|
|
|
|
for i, source := range sources {
|
|
if i > 0 {
|
|
sb.WriteString(",\n")
|
|
}
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\tif %s != _|_ && %s.%s != _|_ for v in %s.%s {\n",
|
|
sourceStr, sourceStr, source, sourceStr, source))
|
|
sb.WriteString("\t\t\t\t\t\t{\n")
|
|
|
|
for _, field := range fields {
|
|
// For optional fields like subPath, wrap in conditional
|
|
if field == "subPath" {
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\t\t\tif v.%s != _|_ { %s: v.%s }\n", field, field, field))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\t\t\t%s: v.%s\n", field, field))
|
|
}
|
|
}
|
|
|
|
sb.WriteString("\t\t\t\t\t\t}\n")
|
|
sb.WriteString("\t\t\t\t\t}")
|
|
}
|
|
|
|
sb.WriteString("\n\t\t\t\t]")
|
|
return sb.String()
|
|
}
|
|
|
|
// generateMapBySourceCUE generates CUE for mapping different sources differently.
|
|
func (g *CUEGenerator) generateMapBySourceCUE(sourceStr string, sources []string, mapBySource map[string]FieldMap) string {
|
|
var sb strings.Builder
|
|
sb.WriteString("[\n")
|
|
|
|
for i, source := range sources {
|
|
if i > 0 {
|
|
sb.WriteString(",\n")
|
|
}
|
|
|
|
mapping, hasMapping := mapBySource[source]
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\tif %s != _|_ && %s.%s != _|_ for v in %s.%s {\n",
|
|
sourceStr, sourceStr, source, sourceStr, source))
|
|
sb.WriteString("\t\t\t\t\t\t{\n")
|
|
|
|
if hasMapping {
|
|
for fieldName, fieldVal := range mapping {
|
|
if nf, isNested := fieldVal.(*NestedField); isNested {
|
|
// Inline nested field expansion with correct indentation
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\t\t\t%s: {\n", fieldName))
|
|
for nestedName, nestedVal := range nf.mapping {
|
|
if optField, isOptional := nestedVal.(*OptionalField); isOptional {
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\t\t\t\tif v.%s != _|_ {\n", optField.field))
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\t\t\t\t\t%s: v.%s\n", nestedName, optField.field))
|
|
sb.WriteString("\t\t\t\t\t\t\t\t}\n")
|
|
} else {
|
|
valStr := g.fieldValueToCUE(nestedVal)
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\t\t\t\t%s: %s\n", nestedName, valStr))
|
|
}
|
|
}
|
|
sb.WriteString("\t\t\t\t\t\t\t}\n")
|
|
} else {
|
|
valStr := g.fieldValueToCUE(fieldVal)
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\t\t\t%s: %s\n", fieldName, valStr))
|
|
}
|
|
}
|
|
} else {
|
|
sb.WriteString("\t\t\t\t\t\t\tv\n")
|
|
}
|
|
|
|
sb.WriteString("\t\t\t\t\t\t}\n")
|
|
sb.WriteString("\t\t\t\t\t}")
|
|
}
|
|
|
|
sb.WriteString("\n\t\t\t\t]")
|
|
return sb.String()
|
|
}
|
|
|
|
// fieldValueToCUE converts a FieldValue to CUE syntax.
|
|
func (g *CUEGenerator) fieldValueToCUE(fv FieldValue) string {
|
|
switch val := fv.(type) {
|
|
case FieldRef:
|
|
return "v." + string(val)
|
|
case *OrFieldRef:
|
|
primary := "v." + string(val.primary)
|
|
fallback := g.fieldValueToCUE(val.fallback)
|
|
return fmt.Sprintf("*%s | %s", primary, fallback)
|
|
case LitVal:
|
|
return formatCUEValue(val.val)
|
|
case *FormatField:
|
|
// Generate strconv/strings expression
|
|
return g.formatFieldToCUE(val)
|
|
case *NestedField:
|
|
return g.nestedFieldToCUE(val)
|
|
default:
|
|
return "_"
|
|
}
|
|
}
|
|
|
|
// formatFieldToCUE converts a FormatField to CUE syntax.
|
|
func (g *CUEGenerator) formatFieldToCUE(ff *FormatField) string {
|
|
// Simple case: "port-%v" with one arg
|
|
if ff.format == "port-%v" && len(ff.args) == 1 {
|
|
arg := g.fieldValueToCUE(ff.args[0])
|
|
return fmt.Sprintf(`"port-" + strconv.FormatInt(%s, 10)`, arg)
|
|
}
|
|
return fmt.Sprintf("%q", ff.format)
|
|
}
|
|
|
|
// nestedFieldToCUE converts a NestedField to CUE syntax.
|
|
func (g *CUEGenerator) nestedFieldToCUE(nf *NestedField) string {
|
|
var sb strings.Builder
|
|
sb.WriteString("{\n")
|
|
for fieldName, fieldVal := range nf.mapping {
|
|
// Handle optional fields specially - generate conditional inclusion
|
|
if optField, isOptional := fieldVal.(*OptionalField); isOptional {
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\tif v.%s != _|_ {\n", optField.field))
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t\t%s: v.%s\n", fieldName, optField.field))
|
|
sb.WriteString("\t\t\t\t}\n")
|
|
} else {
|
|
valStr := g.fieldValueToCUE(fieldVal)
|
|
sb.WriteString(fmt.Sprintf("\t\t\t\t%s: %s\n", fieldName, valStr))
|
|
}
|
|
}
|
|
sb.WriteString("\t\t\t}")
|
|
return sb.String()
|
|
}
|
|
|
|
// concatExprToCUE converts a ConcatExprValue to CUE syntax.
|
|
// This generates: mountsArray.pvc + mountsArray.configMap + mountsArray.secret + ...
|
|
func (g *CUEGenerator) concatExprToCUE(ce *ConcatExprValue) string {
|
|
sourceName := ce.Source().HelperName()
|
|
fields := ce.Fields()
|
|
|
|
if len(fields) == 0 {
|
|
return "[]"
|
|
}
|
|
|
|
// Build the concatenation expression: mountsArray.pvc + mountsArray.configMap + ...
|
|
var parts []string
|
|
for _, field := range fields {
|
|
parts = append(parts, fmt.Sprintf("%s.%s", sourceName, field))
|
|
}
|
|
|
|
return strings.Join(parts, " + ")
|
|
}
|
|
|
|
// conditionToCUE converts a Condition to CUE syntax.
|
|
func (g *CUEGenerator) conditionToCUE(cond Condition) string {
|
|
switch c := cond.(type) {
|
|
case *IsSetCondition:
|
|
return fmt.Sprintf("parameter[%q] != _|_", c.ParamName())
|
|
case *ParamPathIsSetCondition:
|
|
// Check if a nested parameter path is set: parameter.path != _|_
|
|
return fmt.Sprintf("parameter.%s != _|_", c.Path())
|
|
case *TruthyCondition:
|
|
return fmt.Sprintf("parameter.%s", c.ParamName())
|
|
case *FalsyCondition:
|
|
return fmt.Sprintf("!parameter.%s", c.ParamName())
|
|
case *InCondition:
|
|
return g.inConditionToCUE(c)
|
|
case *StringContainsCondition:
|
|
return fmt.Sprintf(`strings.Contains(parameter.%s, %q)`, c.ParamName(), c.Substr())
|
|
case *StringMatchesCondition:
|
|
return fmt.Sprintf(`parameter.%s =~ %q`, c.ParamName(), c.Pattern())
|
|
case *StringStartsWithCondition:
|
|
return fmt.Sprintf(`strings.HasPrefix(parameter.%s, %q)`, c.ParamName(), c.Prefix())
|
|
case *StringEndsWithCondition:
|
|
return fmt.Sprintf(`strings.HasSuffix(parameter.%s, %q)`, c.ParamName(), c.Suffix())
|
|
case *LenCondition:
|
|
return fmt.Sprintf("len(parameter.%s) %s %d", c.ParamName(), c.Op(), c.Length())
|
|
case *ArrayContainsCondition:
|
|
return fmt.Sprintf("list.Contains(parameter.%s, %s)", c.ParamName(), formatCUEValue(c.Value()))
|
|
case *MapHasKeyCondition:
|
|
return fmt.Sprintf("parameter.%s.%s != _|_", c.ParamName(), c.Key())
|
|
case *ParamCompareCondition:
|
|
// Parameter comparison: parameter.name op value
|
|
return fmt.Sprintf("parameter.%s %s %s", c.ParamName(), c.Op(), formatCUEValue(c.CompareValue()))
|
|
case *Comparison:
|
|
left := g.exprToCUE(c.Left())
|
|
right := g.exprToCUE(c.Right())
|
|
return fmt.Sprintf("%s %s %s", left, c.Op(), right)
|
|
case *CompareCondition:
|
|
left := g.anyToCUE(c.Left())
|
|
right := g.anyToCUE(c.Right())
|
|
return fmt.Sprintf("%s %s %s", left, c.Operator(), right)
|
|
case *AndCondition:
|
|
left := g.conditionToCUE(c.left)
|
|
right := g.conditionToCUE(c.right)
|
|
return fmt.Sprintf("(%s) && (%s)", left, right)
|
|
case *OrCondition:
|
|
left := g.conditionToCUE(c.left)
|
|
right := g.conditionToCUE(c.right)
|
|
return fmt.Sprintf("(%s) || (%s)", left, right)
|
|
case *NotCondition:
|
|
// Special case: Not(IsSet("x")) -> parameter["x"] == _|_ (cleaner than !(parameter["x"] != _|_))
|
|
if isSet, ok := c.Inner().(*IsSetCondition); ok {
|
|
return fmt.Sprintf("parameter[%q] == _|_", isSet.ParamName())
|
|
}
|
|
inner := g.conditionToCUE(c.Inner())
|
|
return fmt.Sprintf("!(%s)", inner)
|
|
case *LogicalExpr:
|
|
parts := make([]string, len(c.Conditions()))
|
|
for i, sub := range c.Conditions() {
|
|
parts[i] = g.conditionToCUE(sub)
|
|
}
|
|
op := " && "
|
|
if c.Op() == OpOr {
|
|
op = " || "
|
|
}
|
|
return strings.Join(parts, op)
|
|
case *NotExpr:
|
|
return fmt.Sprintf("!(%s)", g.conditionToCUE(c.Cond()))
|
|
case *HasExposedPortsCondition:
|
|
// Check if any port has expose=true
|
|
portsStr := g.valueToCUE(c.Ports())
|
|
return fmt.Sprintf("len([for p in %s if p.expose == true { p }]) > 0", portsStr)
|
|
case *LenNotZeroCondition:
|
|
// 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())
|
|
case *ContextPathExistsCondition:
|
|
// Check if a context output path exists
|
|
return fmt.Sprintf("%s != _|_", c.FullPath())
|
|
case *ContextOutputExistsCondition:
|
|
// Check if a context.output path exists
|
|
return fmt.Sprintf("context.output.%s != _|_", c.Path())
|
|
case *AllConditionsCondition:
|
|
// Generate compound condition: if cond1 if cond2 ...
|
|
var parts []string
|
|
for _, cond := range c.Conditions() {
|
|
parts = append(parts, g.conditionToCUE(cond))
|
|
}
|
|
// For CUE, we generate: cond1 && cond2 && cond3
|
|
// which will be used in a single if statement
|
|
return strings.Join(parts, " && ")
|
|
default:
|
|
return cueBoolTrue
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
return g.valueToCUE(v)
|
|
}
|
|
return "_"
|
|
}
|
|
|
|
// anyToCUE converts any value to CUE syntax.
|
|
func (g *CUEGenerator) anyToCUE(v any) string {
|
|
if val, ok := v.(Value); ok {
|
|
return g.valueToCUE(val)
|
|
}
|
|
return formatCUEValue(v)
|
|
}
|
|
|
|
// writeWorkload writes the workload definition.
|
|
func (g *CUEGenerator) writeWorkload(sb *strings.Builder, c *ComponentDefinition, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
workload := c.GetWorkload()
|
|
|
|
// Check for autodetect workload type
|
|
if workload.IsAutodetect() {
|
|
sb.WriteString(fmt.Sprintf("%sworkload: type: %q\n", indent, "autodetects.core.oam.dev"))
|
|
return
|
|
}
|
|
|
|
sb.WriteString(fmt.Sprintf("%sworkload: {\n", indent))
|
|
sb.WriteString(fmt.Sprintf("%s%sdefinition: {\n", indent, g.indent))
|
|
sb.WriteString(fmt.Sprintf("%s%s%sapiVersion: %q\n", indent, g.indent, g.indent, workload.APIVersion()))
|
|
sb.WriteString(fmt.Sprintf("%s%s%skind: %q\n", indent, g.indent, g.indent, workload.Kind()))
|
|
sb.WriteString(fmt.Sprintf("%s%s}\n", indent, g.indent))
|
|
|
|
// Write workload type
|
|
workloadType := g.inferWorkloadType(workload)
|
|
sb.WriteString(fmt.Sprintf("%s%stype: %q\n", indent, g.indent, workloadType))
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
}
|
|
|
|
// inferWorkloadType infers the workload type from API version and kind.
|
|
func (g *CUEGenerator) inferWorkloadType(w WorkloadType) string {
|
|
switch {
|
|
case w.APIVersion() == "apps/v1" && w.Kind() == "Deployment":
|
|
return "deployments.apps"
|
|
case w.APIVersion() == "apps/v1" && w.Kind() == "StatefulSet":
|
|
return "statefulsets.apps"
|
|
case w.APIVersion() == "apps/v1" && w.Kind() == "DaemonSet":
|
|
return "daemonsets.apps"
|
|
case w.APIVersion() == "batch/v1" && w.Kind() == "Job":
|
|
return "jobs.batch"
|
|
case w.APIVersion() == "batch/v1" && w.Kind() == "CronJob":
|
|
return "cronjobs.batch"
|
|
default:
|
|
return strings.ToLower(w.Kind()) + "s." + strings.Split(w.APIVersion(), "/")[0]
|
|
}
|
|
}
|
|
|
|
// writeStatus writes the status configuration.
|
|
func (g *CUEGenerator) writeStatus(sb *strings.Builder, c *ComponentDefinition, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
|
|
customStatus := c.GetCustomStatus()
|
|
healthPolicy := c.GetHealthPolicy()
|
|
|
|
if customStatus == "" && healthPolicy == "" {
|
|
return
|
|
}
|
|
|
|
sb.WriteString(fmt.Sprintf("%sstatus: {\n", indent))
|
|
|
|
if customStatus != "" {
|
|
sb.WriteString(fmt.Sprintf("%s%scustomStatus: #\"\"\"\n", indent, g.indent))
|
|
for _, line := range strings.Split(customStatus, "\n") {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s%s\n", indent, g.indent, g.indent, line))
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s%s%s\"\"\"#\n", indent, g.indent, g.indent))
|
|
}
|
|
|
|
if healthPolicy != "" {
|
|
sb.WriteString(fmt.Sprintf("%s%shealthPolicy: #\"\"\"\n", indent, g.indent))
|
|
for _, line := range strings.Split(healthPolicy, "\n") {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s%s\n", indent, g.indent, g.indent, line))
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s%s%s\"\"\"#\n", indent, g.indent, g.indent))
|
|
}
|
|
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
}
|
|
|
|
// writeParam writes a single parameter definition.
|
|
func (g *CUEGenerator) writeParam(sb *strings.Builder, param Param, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
|
|
// Write description as comment if present
|
|
if desc := param.GetDescription(); desc != "" {
|
|
sb.WriteString(fmt.Sprintf("%s// +usage=%s\n", indent, desc))
|
|
}
|
|
|
|
name := param.Name()
|
|
optional := "?"
|
|
forceOptional := false
|
|
if bp, ok := param.(interface{ IsForceOptional() bool }); ok {
|
|
forceOptional = bp.IsForceOptional()
|
|
}
|
|
if param.IsRequired() || (param.HasDefault() && !forceOptional) {
|
|
// No ? for required fields or fields with defaults (defaults make them effectively present)
|
|
// Unless forceOptional is set, which keeps ? even with a default
|
|
optional = ""
|
|
}
|
|
|
|
// Handle different parameter types
|
|
switch p := param.(type) {
|
|
case *StringParam:
|
|
g.writeStringParam(sb, p, indent, name, optional)
|
|
case *IntParam:
|
|
g.writeIntParam(sb, p, indent, name, optional)
|
|
case *BoolParam:
|
|
g.writeBoolParam(sb, p, indent, name, optional)
|
|
case *FloatParam:
|
|
g.writeFloatParam(sb, p, indent, name, optional)
|
|
case *ArrayParam:
|
|
g.writeArrayParam(sb, p, indent, name, optional, depth)
|
|
case *MapParam:
|
|
g.writeMapParam(sb, p, indent, name, optional, depth)
|
|
case *StringKeyMapParam:
|
|
g.writeStringKeyMapParam(sb, p, indent, name, optional)
|
|
case *StructParam:
|
|
g.writeStructParam(sb, p, indent, name, optional, depth)
|
|
case *EnumParam:
|
|
g.writeEnumParam(sb, p, indent, name, optional)
|
|
default:
|
|
// Generic fallback
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: _\n", indent, name, optional))
|
|
}
|
|
}
|
|
|
|
// writeStringParam writes a string parameter.
|
|
func (g *CUEGenerator) writeStringParam(sb *strings.Builder, p *StringParam, indent, name, optional string) {
|
|
enumValues := p.GetEnumValues()
|
|
|
|
if len(enumValues) > 0 {
|
|
// Build enum type: "value1" | "value2" | ...
|
|
var enumParts []string
|
|
defaultVal := ""
|
|
if p.HasDefault() {
|
|
if dv, ok := p.GetDefault().(string); ok {
|
|
defaultVal = dv
|
|
}
|
|
}
|
|
|
|
if defaultVal != "" {
|
|
// Add default first with asterisk
|
|
enumParts = append(enumParts, fmt.Sprintf("*%q", defaultVal))
|
|
// Add remaining values (skip default to avoid duplication)
|
|
for _, v := range enumValues {
|
|
if v != defaultVal {
|
|
enumParts = append(enumParts, fmt.Sprintf("%q", v))
|
|
}
|
|
}
|
|
} else {
|
|
// No default, list all values
|
|
for _, v := range enumValues {
|
|
enumParts = append(enumParts, fmt.Sprintf("%q", v))
|
|
}
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, optional, strings.Join(enumParts, " | ")))
|
|
} else {
|
|
// Build constraint parts
|
|
var constraints []string
|
|
|
|
// Pattern constraint: =~"pattern"
|
|
if pattern := p.GetPattern(); pattern != "" {
|
|
constraints = append(constraints, fmt.Sprintf(`=~%q`, pattern))
|
|
}
|
|
|
|
// MinLen constraint: strings.MinRunes(n)
|
|
if minLen := p.GetMinLen(); minLen != nil {
|
|
constraints = append(constraints, fmt.Sprintf("strings.MinRunes(%d)", *minLen))
|
|
}
|
|
|
|
// MaxLen constraint: strings.MaxRunes(n)
|
|
if maxLen := p.GetMaxLen(); maxLen != nil {
|
|
constraints = append(constraints, fmt.Sprintf("strings.MaxRunes(%d)", *maxLen))
|
|
}
|
|
|
|
if p.HasDefault() {
|
|
if len(constraints) > 0 {
|
|
sb.WriteString(fmt.Sprintf("%s%s: *%q | string & %s\n", indent, name, p.GetDefault(), strings.Join(constraints, " & ")))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s: *%q | string\n", indent, name, p.GetDefault()))
|
|
}
|
|
} else {
|
|
if len(constraints) > 0 {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: string & %s\n", indent, name, optional, strings.Join(constraints, " & ")))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: string\n", indent, name, optional))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// writeIntParam writes an integer parameter.
|
|
func (g *CUEGenerator) writeIntParam(sb *strings.Builder, p *IntParam, indent, name, optional string) {
|
|
// Build constraint parts
|
|
var constraints []string
|
|
|
|
// Min constraint: >=n
|
|
if minVal := p.GetMin(); minVal != nil {
|
|
constraints = append(constraints, fmt.Sprintf(">=%d", *minVal))
|
|
}
|
|
|
|
// Max constraint: <=n
|
|
if maxVal := p.GetMax(); maxVal != nil {
|
|
constraints = append(constraints, fmt.Sprintf("<=%d", *maxVal))
|
|
}
|
|
|
|
if p.HasDefault() {
|
|
if len(constraints) > 0 {
|
|
sb.WriteString(fmt.Sprintf("%s%s: *%v | int & %s\n", indent, name, p.GetDefault(), strings.Join(constraints, " & ")))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s: *%v | int\n", indent, name, p.GetDefault()))
|
|
}
|
|
} else {
|
|
if len(constraints) > 0 {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: int & %s\n", indent, name, optional, strings.Join(constraints, " & ")))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: int\n", indent, name, optional))
|
|
}
|
|
}
|
|
}
|
|
|
|
// writeBoolParam writes a boolean parameter.
|
|
func (g *CUEGenerator) writeBoolParam(sb *strings.Builder, p *BoolParam, indent, name, optional string) {
|
|
if p.HasDefault() {
|
|
sb.WriteString(fmt.Sprintf("%s%s: *%v | bool\n", indent, name, p.GetDefault()))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: bool\n", indent, name, optional))
|
|
}
|
|
}
|
|
|
|
// writeFloatParam writes a float parameter.
|
|
func (g *CUEGenerator) writeFloatParam(sb *strings.Builder, p *FloatParam, indent, name, optional string) {
|
|
// Build constraint parts
|
|
var constraints []string
|
|
|
|
// Min constraint: >=n
|
|
if minVal := p.GetMin(); minVal != nil {
|
|
constraints = append(constraints, fmt.Sprintf(">=%v", *minVal))
|
|
}
|
|
|
|
// Max constraint: <=n
|
|
if maxVal := p.GetMax(); maxVal != nil {
|
|
constraints = append(constraints, fmt.Sprintf("<=%v", *maxVal))
|
|
}
|
|
|
|
if p.HasDefault() {
|
|
if len(constraints) > 0 {
|
|
sb.WriteString(fmt.Sprintf("%s%s: *%v | number & %s\n", indent, name, p.GetDefault(), strings.Join(constraints, " & ")))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s: *%v | float\n", indent, name, p.GetDefault()))
|
|
}
|
|
} else {
|
|
if len(constraints) > 0 {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: number & %s\n", indent, name, optional, strings.Join(constraints, " & ")))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: float\n", indent, name, optional))
|
|
}
|
|
}
|
|
}
|
|
|
|
// writeArrayParam writes an array parameter.
|
|
func (g *CUEGenerator) writeArrayParam(sb *strings.Builder, p *ArrayParam, indent, name, optional string, depth int) {
|
|
// Build constraint prefix for MinItems/MaxItems
|
|
var constraints []string
|
|
|
|
// MinItems constraint: list.MinItems(n)
|
|
if minItems := p.GetMinItems(); minItems != nil {
|
|
constraints = append(constraints, fmt.Sprintf("list.MinItems(%d)", *minItems))
|
|
}
|
|
|
|
// MaxItems constraint: list.MaxItems(n)
|
|
if maxItems := p.GetMaxItems(); maxItems != nil {
|
|
constraints = append(constraints, fmt.Sprintf("list.MaxItems(%d)", *maxItems))
|
|
}
|
|
|
|
constraintPrefix := ""
|
|
if len(constraints) > 0 {
|
|
constraintPrefix = strings.Join(constraints, " & ") + " & "
|
|
}
|
|
|
|
// Priority: schemaRef > schema > fields > elementType
|
|
if schemaRef := p.GetSchemaRef(); schemaRef != "" {
|
|
// Reference to a helper definition like #HealthProbe
|
|
// For arrays, output [...#SchemaRef] to indicate an array of the helper type
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: %s[...#%s]\n", indent, name, optional, constraintPrefix, schemaRef))
|
|
return
|
|
}
|
|
|
|
if schema := p.GetSchema(); schema != "" {
|
|
// Raw CUE schema - output directly
|
|
if constraintPrefix != "" {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: %s%s\n", indent, name, optional, constraintPrefix, schema))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, optional, schema))
|
|
}
|
|
return
|
|
}
|
|
|
|
elemType := g.cueTypeForParamType(p.ElementType())
|
|
|
|
// Check if array has structured fields
|
|
if fields := p.GetFields(); len(fields) > 0 {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: %s[...{\n", indent, name, optional, constraintPrefix))
|
|
for _, field := range fields {
|
|
g.writeParam(sb, field, depth+1)
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s}]\n", indent))
|
|
} else if elemType != "" {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: %s[...%s]\n", indent, name, optional, constraintPrefix, elemType))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: %s[...]\n", indent, name, optional, constraintPrefix))
|
|
}
|
|
}
|
|
|
|
// writeMapParam writes a map/object parameter.
|
|
func (g *CUEGenerator) writeMapParam(sb *strings.Builder, p *MapParam, indent, name, optional string, depth int) {
|
|
// Priority: schemaRef > schema > fields > generic
|
|
if schemaRef := p.GetSchemaRef(); schemaRef != "" {
|
|
// Reference to a helper definition like #HealthProbe
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: #%s\n", indent, name, optional, schemaRef))
|
|
return
|
|
}
|
|
|
|
if schema := p.GetSchema(); schema != "" {
|
|
// Raw CUE schema - output directly
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, optional, schema))
|
|
return
|
|
}
|
|
|
|
// Check if map has structured fields
|
|
if fields := p.GetFields(); len(fields) > 0 {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: {\n", indent, name, optional))
|
|
for _, field := range fields {
|
|
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))
|
|
}
|
|
}
|
|
|
|
// writeStringKeyMapParam writes a string-to-string map parameter.
|
|
// Note: description is already written by writeParam, so we don't write it here.
|
|
func (g *CUEGenerator) writeStringKeyMapParam(sb *strings.Builder, _ *StringKeyMapParam, indent, name, optional string) {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: [string]: string\n", indent, name, optional))
|
|
}
|
|
|
|
// writeStructParam writes a struct parameter.
|
|
func (g *CUEGenerator) writeStructParam(sb *strings.Builder, p *StructParam, indent, name, optional string, depth int) {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: {\n", indent, name, optional))
|
|
for _, field := range p.GetFields() {
|
|
g.writeStructField(sb, field, depth+1)
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s}\n", indent))
|
|
}
|
|
|
|
// writeStructField writes a struct field.
|
|
func (g *CUEGenerator) writeStructField(sb *strings.Builder, f *StructField, depth int) {
|
|
indent := strings.Repeat(g.indent, depth)
|
|
|
|
// Write description as comment if present
|
|
if desc := f.GetDescription(); desc != "" {
|
|
sb.WriteString(fmt.Sprintf("%s// +usage=%s\n", indent, desc))
|
|
}
|
|
|
|
name := f.Name()
|
|
optional := "?"
|
|
if f.IsRequired() {
|
|
optional = ""
|
|
}
|
|
|
|
fieldType := g.cueTypeForParamType(f.FieldType())
|
|
|
|
nested := f.GetNested()
|
|
switch {
|
|
case nested != nil:
|
|
if f.FieldType() == ParamTypeArray {
|
|
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 {
|
|
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))
|
|
}
|
|
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))
|
|
}
|
|
}
|
|
|
|
// writeEnumParam writes an enum parameter.
|
|
func (g *CUEGenerator) writeEnumParam(sb *strings.Builder, p *EnumParam, indent, name, optional string) {
|
|
values := p.GetValues()
|
|
if len(values) == 0 {
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: string\n", indent, name, optional))
|
|
return
|
|
}
|
|
|
|
if p.HasDefault() {
|
|
defaultVal := p.GetDefault()
|
|
// Build enum with default: *"default" | "other1" | "other2"
|
|
// Skip the default value in the list to avoid duplication
|
|
var enumParts []string
|
|
enumParts = append(enumParts, fmt.Sprintf("*%q", defaultVal))
|
|
for _, v := range values {
|
|
if v != defaultVal {
|
|
enumParts = append(enumParts, fmt.Sprintf("%q", v))
|
|
}
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, name, strings.Join(enumParts, " | ")))
|
|
} else {
|
|
// Build enum type without default: "value1" | "value2" | ...
|
|
var enumParts []string
|
|
for _, v := range values {
|
|
enumParts = append(enumParts, fmt.Sprintf("%q", v))
|
|
}
|
|
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, optional, strings.Join(enumParts, " | ")))
|
|
}
|
|
}
|
|
|
|
// cueTypeForParamType converts a ParamType to its CUE type string.
|
|
func (g *CUEGenerator) cueTypeForParamType(pt ParamType) string {
|
|
switch pt {
|
|
case ParamTypeString:
|
|
return string(ParamTypeString)
|
|
case ParamTypeInt:
|
|
return "int"
|
|
case ParamTypeBool:
|
|
return "bool"
|
|
case ParamTypeFloat:
|
|
return "float"
|
|
case ParamTypeArray:
|
|
return "[...]"
|
|
case ParamTypeMap:
|
|
return "{...}"
|
|
case ParamTypeStruct:
|
|
return "{...}"
|
|
default:
|
|
return "_"
|
|
}
|
|
}
|
|
|
|
// formatCUEValue formats a Go value as a CUE literal.
|
|
func formatCUEValue(v any) string {
|
|
switch val := v.(type) {
|
|
case string:
|
|
return fmt.Sprintf("%q", val)
|
|
case int, int32, int64, float32, float64:
|
|
return fmt.Sprintf("%v", val)
|
|
case bool:
|
|
return fmt.Sprintf("%v", val)
|
|
default:
|
|
return fmt.Sprintf("%v", val)
|
|
}
|
|
}
|