Feat: introduce Mandatory() API and three-state CUE field markers in defkit (#7068)

* feat(defkit): introduce Mandatory() API and three-state CUE field markers

 Add a new .Mandatory() fluent method to all defkit param types and StructField,
 giving callers explicit control over all three CUE field presence semantics:

   field?: type   — optional, emitted by default (no method call)
   field: type    — non-optional; must have a value, defaults/merging can satisfy (.Mandatory())
   field!: type   — required in input; user must explicitly provide it (.Required())

 Previously, Required() emitted no marker (identical to a mandatory field), making
 it impossible to generate the CUE ! marker at all. Now Required() emits ! and
 Mandatory() emits no marker, matching their intuitive meanings.

 - Add `mandatory bool` field to baseParam and StructField
 - Add IsMandatory() accessor to baseParam, StructField, and Param interface
 - Add .Mandatory() builder method to all 10 concrete param types and StructField
 - Replace the `optional string` variable in cuegen.go with a typed `marker` variable
   backed by named constants (fieldMarkerOptional, fieldMarkerNone, fieldMarkerRequired)
 - Update IsOptional() to return !required && !mandatory
 - Update Optional() to clear mandatory instead of required
 - Update all tests to use .Mandatory() for non-optional fields

Signed-off-by: Jerrin Francis <jerrinfrancis7@gmail.com>

* fix(defkit): correct doc comments after Required/Mandatory semantic swap

  Fix misleading comments that still described the pre-swap behaviour.
  Mandatory() comments incorrectly referenced the "!" marker, and
  Required()/IsRequired() accessors had stale descriptions. Aligned
  all doc comments and field annotations with the actual semantics:
  Required → "!" marker, Mandatory → non-optional (no ? marker).

Signed-off-by: Jerrin Francis <jerrinfrancis7@gmail.com>

* feat(defkit): add BeMandatory matcher and fix BeOptional for three-state semantics

  BeOptional incorrectly returned true for mandatory params by checking
  !IsRequired(). Now uses IsOptional() which correctly excludes both
  required and mandatory params. Adds BeMandatory() matcher and updates
  paramAccessor interface with IsOptional/IsMandatory methods.

Signed-off-by: Jerrin Francis <jerrinfrancis7@gmail.com>

* fix(defkit): clear required flag in Optional() for all param types

  The previous commit only fixed StringParam.Optional(). Apply the same
  fix to Int, Bool, Float, Array, Map, Struct, StructField, Enum, OneOf,
  and StringKeyMap so that Optional() consistently resets both mandatory
  and required flags across all parameter types.

Signed-off-by: Jerrin Francis <jerrinfrancis7@gmail.com>

* fix(defkit): make Required() and Mandatory() mutually exclusive and fix missing optional markers in CUE generation

  Required() and Mandatory() only set their own flag without clearing
  the other, allowing both to be true simultaneously via chaining.
  Each method now clears the opposite flag so the last call wins,
  matching the reset semantics that Optional() already followed.

  Also fix CUE generation for int, bool, float, and enum params where
  the optional marker was dropped from format strings when a default
  value was present, causing those fields to always render without
  the field marker regardless of their optional/mandatory/required state.

Signed-off-by: Jerrin Francis <jerrinfrancis7@gmail.com>

* Empty commit to re-trigger e2e

Signed-off-by: Jerrin Francis <jerrinfrancis7@gmail.com>

---------

Signed-off-by: Jerrin Francis <jerrinfrancis7@gmail.com>
This commit is contained in:
Jerrin Francis
2026-03-12 16:39:34 +00:00
committed by GitHub
parent 79d1ee7029
commit cf66f20cd9
17 changed files with 391 additions and 133 deletions
+9 -9
View File
@@ -477,7 +477,7 @@ var _ = Describe("ArrayBuilder CUE Generation", func() {
It("should generate CUE for ForEachWith with simple field assignments", func() {
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
defkit.String("name"),
)
comp := defkit.NewComponent("test").
@@ -504,7 +504,7 @@ var _ = Describe("ArrayBuilder CUE Generation", func() {
It("should generate CUE for ForEachWith with IfSet/IfNotSet conditionals", func() {
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
defkit.Int("containerPort"),
)
comp := defkit.NewComponent("test").
@@ -536,7 +536,7 @@ var _ = Describe("ArrayBuilder CUE Generation", func() {
It("should generate CUE for ForEachWith with let bindings and defaults", func() {
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
)
comp := defkit.NewComponent("test").
Workload("apps/v1", "Deployment").
@@ -562,7 +562,7 @@ var _ = Describe("ArrayBuilder CUE Generation", func() {
It("should generate CUE for ForEachWithVar with custom variable name", func() {
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
)
comp := defkit.NewComponent("test").
Workload("apps/v1", "Deployment").
@@ -586,7 +586,7 @@ var _ = Describe("ArrayBuilder CUE Generation", func() {
It("should generate CUE for ForEachWithGuardedFiltered with guard and filter", func() {
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
defkit.Bool("expose").Default(false),
)
comp := defkit.NewComponent("test").
@@ -620,7 +620,7 @@ var _ = Describe("ArrayBuilder CUE Generation", func() {
It("should generate CUE for ForEachWith with nested If conditions", func() {
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
defkit.String("protocol"),
)
exposeType := defkit.String("exposeType")
@@ -650,7 +650,7 @@ var _ = Describe("ArrayBuilder CUE Generation", func() {
It("should auto-detect strconv import from ForEachWith ItemBuilder ops", func() {
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
)
comp := defkit.NewComponent("test").
Workload("apps/v1", "Deployment").
@@ -674,7 +674,7 @@ var _ = Describe("ArrayBuilder CUE Generation", func() {
It("should auto-detect strings import from ForEachWith ItemBuilder ops", func() {
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
defkit.String("protocol"),
)
comp := defkit.NewComponent("test").
@@ -699,7 +699,7 @@ var _ = Describe("ArrayBuilder CUE Generation", func() {
It("should generate CUE for helper backed by FromArray with ArrayBuilder", func() {
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
defkit.Bool("expose").Default(false),
)
comp := defkit.NewComponent("test").
+1 -1
View File
@@ -301,7 +301,7 @@ var _ = Describe("Collections", func() {
Expect(compOpt).NotTo(BeNil())
// Verify it can be used in a FieldMap and the CUE generation picks it up
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
defkit.Int("nodePort"),
)
col := defkit.Each(ports).Map(defkit.FieldMap{
+5 -5
View File
@@ -50,7 +50,7 @@ var _ = Describe("ComponentDefinition", func() {
})
It("should add parameters", func() {
image := defkit.String("image").Required()
image := defkit.String("image").Mandatory()
replicas := defkit.Int("replicas").Default(1)
c := defkit.NewComponent("webservice").
Params(image, replicas)
@@ -519,7 +519,7 @@ var _ = Describe("ComponentDefinition", func() {
c := defkit.NewComponent("webservice").
Description("Web service component").
Workload("apps/v1", "Deployment").
Params(defkit.String("image").Required())
Params(defkit.String("image").Mandatory())
yamlBytes, err := c.ToYAML()
Expect(err).NotTo(HaveOccurred())
@@ -547,7 +547,7 @@ var _ = Describe("ComponentDefinition", func() {
It("should generate parameter schema CUE", func() {
c := defkit.NewComponent("test").
Params(
defkit.String("image").Required(),
defkit.String("image").Mandatory(),
defkit.Int("replicas").Default(1),
)
@@ -563,7 +563,7 @@ var _ = Describe("ComponentDefinition", func() {
Description("Web service component").
Workload("apps/v1", "Deployment").
Params(
defkit.String("image").Required().Description("Container image"),
defkit.String("image").Mandatory().Description("Container image"),
defkit.Int("replicas").Default(1),
)
@@ -644,7 +644,7 @@ var _ = Describe("ComponentDefinition", func() {
Context("Full Component Example", func() {
It("should build a complete webservice component", func() {
image := defkit.String("image").Required().Description("Container image")
image := defkit.String("image").Mandatory().Description("Container image")
replicas := defkit.Int("replicas").Default(1)
port := defkit.Int("port").Default(80)
+1 -1
View File
@@ -256,7 +256,7 @@ var _ = Describe("TestContext", func() {
)
BeforeEach(func() {
image = defkit.String("image").Required()
image = defkit.String("image").Mandatory()
replicas = defkit.Int("replicas").Default(1)
port = defkit.Int("port").Default(80)
+57 -38
View File
@@ -25,6 +25,13 @@ import (
// cueOpenStruct is the CUE literal for an open struct type.
const cueOpenStruct = "{...}"
// CUE field marker constants express the three field presence semantics.
const (
fieldMarkerOptional = "?" // field?: type — field may be absent from input
fieldMarkerNone = "" // field: type — field must have a value; defaults/merging can satisfy
fieldMarkerRequired = "!" // field!: type — field must be explicitly provided in user input
)
// 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 {
@@ -504,9 +511,11 @@ func (g *CUEGenerator) writeStructFieldForHelper(sb *strings.Builder, f *StructF
}
name := f.Name()
optional := "?"
marker := fieldMarkerOptional
if f.IsRequired() {
optional = ""
marker = fieldMarkerRequired
} else if f.IsMandatory() {
marker = fieldMarkerNone
}
// Check if this field references another helper type
@@ -517,13 +526,13 @@ func (g *CUEGenerator) writeStructFieldForHelper(sb *strings.Builder, f *StructF
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))
sb.WriteString(fmt.Sprintf("%s%s%s: [...#%s]\n", indent, name, marker, 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))
sb.WriteString(fmt.Sprintf("%s%s%s: #%s\n", indent, name, marker, schemaRef))
}
}
return
@@ -533,13 +542,13 @@ func (g *CUEGenerator) writeStructFieldForHelper(sb *strings.Builder, f *StructF
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))
sb.WriteString(fmt.Sprintf("%s%s%s: [...{\n", indent, name, marker))
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))
sb.WriteString(fmt.Sprintf("%s%s%s: {\n", indent, name, marker))
for _, nestedField := range nested.GetFields() {
g.writeStructFieldForHelper(sb, nestedField, depth+1)
}
@@ -574,16 +583,16 @@ func (g *CUEGenerator) writeStructFieldForHelper(sb *strings.Builder, f *StructF
}
case f.FieldType() == ParamTypeArray && f.GetElementType() != "":
elemCUE := g.cueTypeForParamType(f.GetElementType())
sb.WriteString(fmt.Sprintf("%s%s%s: [...%s]\n", indent, name, optional, elemCUE))
sb.WriteString(fmt.Sprintf("%s%s%s: [...%s]\n", indent, name, marker, elemCUE))
case len(f.GetEnumValues()) > 0:
// Enum without default: "value1" | "value2"
var enumParts []string
for _, v := range f.GetEnumValues() {
enumParts = append(enumParts, fmt.Sprintf("%q", v))
}
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, optional, strings.Join(enumParts, " | ")))
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, marker, strings.Join(enumParts, " | ")))
default:
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, optional, fieldType))
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, marker, fieldType))
}
}
@@ -2753,42 +2762,50 @@ func (g *CUEGenerator) writeParam(sb *strings.Builder, param Param, depth int) {
}
name := param.Name()
optional := "?"
marker := fieldMarkerOptional
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 = ""
if param.IsRequired() {
// "!" marker: user must explicitly provide this field in input
marker = fieldMarkerRequired
} else {
isMandatory := false
if bm, ok := param.(interface{ IsMandatory() bool }); ok {
isMandatory = bm.IsMandatory()
}
if isMandatory || (param.HasDefault() && !forceOptional) {
// No marker: field must have a value, but defaults/merging can satisfy it
marker = fieldMarkerNone
}
}
// Handle different parameter types
switch p := param.(type) {
case *StringParam:
g.writeStringParam(sb, p, indent, name, optional)
g.writeStringParam(sb, p, indent, name, marker)
case *IntParam:
g.writeIntParam(sb, p, indent, name, optional)
g.writeIntParam(sb, p, indent, name, marker)
case *BoolParam:
g.writeBoolParam(sb, p, indent, name, optional)
g.writeBoolParam(sb, p, indent, name, marker)
case *FloatParam:
g.writeFloatParam(sb, p, indent, name, optional)
g.writeFloatParam(sb, p, indent, name, marker)
case *ArrayParam:
g.writeArrayParam(sb, p, indent, name, optional, depth)
g.writeArrayParam(sb, p, indent, name, marker, depth)
case *MapParam:
g.writeMapParam(sb, p, indent, name, optional, depth)
g.writeMapParam(sb, p, indent, name, marker, depth)
case *StringKeyMapParam:
g.writeStringKeyMapParam(sb, p, indent, name, optional)
g.writeStringKeyMapParam(sb, p, indent, name, marker)
case *StructParam:
g.writeStructParam(sb, p, indent, name, optional, depth)
g.writeStructParam(sb, p, indent, name, marker, depth)
case *EnumParam:
g.writeEnumParam(sb, p, indent, name, optional)
g.writeEnumParam(sb, p, indent, name, marker)
case *OneOfParam:
g.writeOneOfParam(sb, p, indent, name, optional, depth)
g.writeOneOfParam(sb, p, indent, name, marker, depth)
default:
// Generic fallback
sb.WriteString(fmt.Sprintf("%s%s%s: _\n", indent, name, optional))
sb.WriteString(fmt.Sprintf("%s%s%s: _\n", indent, name, marker))
}
}
@@ -2877,9 +2894,9 @@ func (g *CUEGenerator) writeIntParam(sb *strings.Builder, p *IntParam, indent, n
if p.HasDefault() {
if len(constraints) > 0 {
sb.WriteString(fmt.Sprintf("%s%s: *%v | int & %s\n", indent, name, p.GetDefault(), strings.Join(constraints, " & ")))
sb.WriteString(fmt.Sprintf("%s%s%s: *%v | int & %s\n", indent, name, optional, p.GetDefault(), strings.Join(constraints, " & ")))
} else {
sb.WriteString(fmt.Sprintf("%s%s: *%v | int\n", indent, name, p.GetDefault()))
sb.WriteString(fmt.Sprintf("%s%s%s: *%v | int\n", indent, name, optional, p.GetDefault()))
}
} else {
if len(constraints) > 0 {
@@ -2893,7 +2910,7 @@ func (g *CUEGenerator) writeIntParam(sb *strings.Builder, p *IntParam, indent, n
// 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()))
sb.WriteString(fmt.Sprintf("%s%s%s: *%v | bool\n", indent, name, optional, p.GetDefault()))
} else {
sb.WriteString(fmt.Sprintf("%s%s%s: bool\n", indent, name, optional))
}
@@ -2916,9 +2933,9 @@ func (g *CUEGenerator) writeFloatParam(sb *strings.Builder, p *FloatParam, inden
if p.HasDefault() {
if len(constraints) > 0 {
sb.WriteString(fmt.Sprintf("%s%s: *%v | number & %s\n", indent, name, p.GetDefault(), strings.Join(constraints, " & ")))
sb.WriteString(fmt.Sprintf("%s%s%s: *%v | number & %s\n", indent, name, optional, p.GetDefault(), strings.Join(constraints, " & ")))
} else {
sb.WriteString(fmt.Sprintf("%s%s: *%v | float\n", indent, name, p.GetDefault()))
sb.WriteString(fmt.Sprintf("%s%s%s: *%v | float\n", indent, name, optional, p.GetDefault()))
}
} else {
if len(constraints) > 0 {
@@ -3044,9 +3061,11 @@ func (g *CUEGenerator) writeStructField(sb *strings.Builder, f *StructField, dep
}
name := f.Name()
optional := "?"
marker := fieldMarkerOptional
if f.IsRequired() {
optional = ""
marker = fieldMarkerRequired
} else if f.IsMandatory() {
marker = fieldMarkerNone
}
fieldType := g.cueTypeForParamType(f.FieldType())
@@ -3055,13 +3074,13 @@ func (g *CUEGenerator) writeStructField(sb *strings.Builder, f *StructField, dep
switch {
case nested != nil:
if f.FieldType() == ParamTypeArray {
sb.WriteString(fmt.Sprintf("%s%s%s: [...{\n", indent, name, optional))
sb.WriteString(fmt.Sprintf("%s%s%s: [...{\n", indent, name, marker))
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))
sb.WriteString(fmt.Sprintf("%s%s%s: {\n", indent, name, marker))
for _, nestedField := range nested.GetFields() {
g.writeStructField(sb, nestedField, depth+1)
}
@@ -3089,16 +3108,16 @@ func (g *CUEGenerator) writeStructField(sb *strings.Builder, f *StructField, dep
}
case f.FieldType() == ParamTypeArray && f.GetElementType() != "":
elemCUE := g.cueTypeForParamType(f.GetElementType())
sb.WriteString(fmt.Sprintf("%s%s%s: [...%s]\n", indent, name, optional, elemCUE))
sb.WriteString(fmt.Sprintf("%s%s%s: [...%s]\n", indent, name, marker, elemCUE))
case len(f.GetEnumValues()) > 0:
// Enum without default: "value1" | "value2"
var enumParts []string
for _, v := range f.GetEnumValues() {
enumParts = append(enumParts, fmt.Sprintf("%q", v))
}
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, optional, strings.Join(enumParts, " | ")))
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, marker, strings.Join(enumParts, " | ")))
default:
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, optional, fieldType))
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, marker, fieldType))
}
}
@@ -3121,7 +3140,7 @@ func (g *CUEGenerator) writeEnumParam(sb *strings.Builder, p *EnumParam, indent,
enumParts = append(enumParts, fmt.Sprintf("%q", v))
}
}
sb.WriteString(fmt.Sprintf("%s%s: %s\n", indent, name, strings.Join(enumParts, " | ")))
sb.WriteString(fmt.Sprintf("%s%s%s: %s\n", indent, name, optional, strings.Join(enumParts, " | ")))
} else {
// Build enum type without default: "value1" | "value2" | ...
var enumParts []string
+70 -19
View File
@@ -36,7 +36,7 @@ var _ = Describe("CUEGenerator", func() {
It("should generate CUE for string parameters", func() {
comp := defkit.NewComponent("test").
Params(
defkit.String("image").Required().Description("Container image"),
defkit.String("image").Mandatory().Description("Container image"),
defkit.String("tag").Default("latest").Description("Image tag"),
)
@@ -52,7 +52,7 @@ var _ = Describe("CUEGenerator", func() {
comp := defkit.NewComponent("test").
Params(
defkit.Int("replicas").Default(1).Description("Number of replicas"),
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
)
cue := gen.GenerateParameterSchema(comp)
@@ -110,7 +110,7 @@ var _ = Describe("CUEGenerator", func() {
Expect(cue).To(ContainSubstring("labels?: [string]: string"))
})
It("should mark required parameters without ?", func() {
It("should emit ! for required parameters", func() {
comp := defkit.NewComponent("test").
Params(
defkit.String("required").Required(),
@@ -119,10 +119,61 @@ var _ = Describe("CUEGenerator", func() {
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring("required: string"))
Expect(cue).To(ContainSubstring("required!: string"))
Expect(cue).To(ContainSubstring("optional?: string"))
})
It("should handle all three field presence markers", func() {
comp := defkit.NewComponent("test").
Params(
defkit.String("accessKey").Required(), // ! — user must explicitly provide
defkit.Int("port").Mandatory(), // no marker — must have value, defaults can satisfy
defkit.Bool("enabled").Mandatory(), // no marker
defkit.String("tag"), // ? — fully optional
)
cue := gen.GenerateParameterSchema(comp)
// Required fields get "!" marker
Expect(cue).To(ContainSubstring("accessKey!: string"))
// Mandatory fields get no marker (no ? and no !)
Expect(cue).To(ContainSubstring("port: int"))
Expect(cue).NotTo(ContainSubstring("port!:"))
Expect(cue).NotTo(ContainSubstring("port?:"))
Expect(cue).To(ContainSubstring("enabled: bool"))
// Plain optional gets ?
Expect(cue).To(ContainSubstring("tag?: string"))
})
It("should emit ! for required parameters with defaults", func() {
comp := defkit.NewComponent("test").
Params(
defkit.String("accessKey").Required().Default("key123"),
)
cue := gen.GenerateParameterSchema(comp)
// Required with default: "!" wins, default value is still in the CUE type
Expect(cue).To(ContainSubstring(`accessKey!: *"key123" | string`))
})
It("should emit ! for required non-string parameters with defaults", func() {
comp := defkit.NewComponent("test").
Params(
defkit.Int("port").Required().Default(8080),
defkit.Bool("enabled").Required().Default(true),
defkit.Float("ratio").Required().Default(0.5),
defkit.Enum("mode").Required().Default("fast").Values("fast", "slow"),
)
cue := gen.GenerateParameterSchema(comp)
Expect(cue).To(ContainSubstring(`port!: *8080 | int`))
Expect(cue).To(ContainSubstring(`enabled!: *true | bool`))
Expect(cue).To(ContainSubstring(`ratio!: *0.5 | float`))
Expect(cue).To(ContainSubstring(`mode!: *"fast" | "slow"`))
})
It("should keep ? for ForceOptional parameters even with defaults", func() {
comp := defkit.NewComponent("test").
Params(
@@ -167,7 +218,7 @@ var _ = Describe("CUEGenerator", func() {
It("should generate // +short directive for params with short flags", func() {
comp := defkit.NewComponent("test").
Params(
defkit.String("image").Required().Description("Container image").Short("i"),
defkit.String("image").Mandatory().Description("Container image").Short("i"),
)
cue := gen.GenerateParameterSchema(comp)
@@ -241,7 +292,7 @@ var _ = Describe("CUEGenerator", func() {
comp := defkit.NewComponent("test").
Params(
defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
defkit.String("name"),
defkit.Enum("protocol").Values("TCP", "UDP").Default("TCP"),
),
@@ -261,7 +312,7 @@ var _ = Describe("CUEGenerator", func() {
defkit.Struct("selector").WithFields(
defkit.Field("matchExpressions", defkit.ParamTypeArray).
Nested(defkit.Struct("matchExpression").WithFields(
defkit.Field("key", defkit.ParamTypeString).Required(),
defkit.Field("key", defkit.ParamTypeString).Mandatory(),
defkit.Field("operator", defkit.ParamTypeString),
)),
),
@@ -284,7 +335,7 @@ var _ = Describe("CUEGenerator", func() {
Description("Volume type").
Variants(
defkit.Variant("pvc").WithFields(
defkit.Field("claimName", defkit.ParamTypeString).Required(),
defkit.Field("claimName", defkit.ParamTypeString).Mandatory(),
),
defkit.Variant("emptyDir").WithFields(
defkit.Field("medium", defkit.ParamTypeString).Default("").Values("", "Memory"),
@@ -308,10 +359,10 @@ var _ = Describe("CUEGenerator", func() {
comp := defkit.NewComponent("test").
Params(
defkit.List("volumes").WithFields(
defkit.String("name").Required(),
defkit.String("name").Mandatory(),
defkit.OneOf("type").Default("emptyDir").Variants(
defkit.Variant("pvc").WithFields(
defkit.Field("claimName", defkit.ParamTypeString).Required(),
defkit.Field("claimName", defkit.ParamTypeString).Mandatory(),
),
defkit.Variant("emptyDir"),
),
@@ -335,7 +386,7 @@ var _ = Describe("CUEGenerator", func() {
Variants(
defkit.Variant("simple"), // no fields
defkit.Variant("complex").WithFields(
defkit.Field("config", defkit.ParamTypeString).Required(),
defkit.Field("config", defkit.ParamTypeString).Mandatory(),
),
),
)
@@ -687,7 +738,7 @@ var _ = Describe("CUEGenerator", func() {
Description("Web service component").
Workload("apps/v1", "Deployment").
Params(
defkit.String("image").Required(),
defkit.String("image").Mandatory(),
)
cue := gen.GenerateFullDefinition(comp)
@@ -866,7 +917,7 @@ var _ = Describe("CUEGenerator", func() {
gen := defkit.NewCUEGenerator()
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
defkit.String("name"),
)
comp := defkit.NewComponent("test").
@@ -930,7 +981,7 @@ var _ = Describe("CUEGenerator", func() {
Values("ClusterIP", "NodePort", "LoadBalancer").
Default("ClusterIP")
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
defkit.String("name"),
defkit.Int("nodePort"),
defkit.Bool("expose").Default(false),
@@ -966,7 +1017,7 @@ var _ = Describe("CUEGenerator", func() {
Values("ClusterIP", "NodePort", "LoadBalancer").
Default("ClusterIP")
ports := defkit.List("ports").WithFields(
defkit.Int("port").Required(),
defkit.Int("port").Mandatory(),
defkit.Int("nodePort"),
defkit.String("protocol"),
defkit.Bool("expose").Default(false),
@@ -1082,14 +1133,14 @@ var _ = Describe("CUEGenerator", func() {
Expect(cue).NotTo(ContainSubstring("propagation?: string"))
})
It("should generate required enum without default on a helper struct field", func() {
It("should generate mandatory enum without default on a helper struct field", func() {
rule := defkit.Struct("rule").WithFields(
defkit.Field("mode", defkit.ParamTypeString).
Values("strict", "permissive").
Required(),
Mandatory(),
)
p := defkit.NewPolicy("test-enum-required").
p := defkit.NewPolicy("test-enum-mandatory").
Description("Test").
Helper("Rule", rule)
@@ -1110,7 +1161,7 @@ var _ = Describe("CUEGenerator", func() {
Optional(),
defkit.Field("mode", defkit.ParamTypeString).
Values("fast", "safe").
Required(),
Mandatory(),
),
).
Template(func(tpl *defkit.Template) {
@@ -102,7 +102,7 @@ var _ = Describe("HelperDefinition", func() {
helper := defkit.Map("labelSelector").WithFields(
defkit.StringKeyMap("matchLabels").Description("A map of {key,value} pairs"),
defkit.Array("matchExpressions").Description("Label selector requirements").WithFields(
defkit.String("key").Required(),
defkit.String("key").Mandatory(),
defkit.String("operator").Default("In").Values("In", "NotIn", "Exists", "DoesNotExist"),
defkit.Array("values").Of(defkit.ParamTypeString),
),
@@ -133,7 +133,7 @@ var _ = Describe("HelperDefinition", func() {
It("should render Of(ParamTypeString) in Struct-based helper fields", func() {
helper := defkit.Struct("nodeSelector").WithFields(
defkit.Field("key", defkit.ParamTypeString).Required(),
defkit.Field("key", defkit.ParamTypeString).Mandatory(),
defkit.Field("operator", defkit.ParamTypeString).Default("In").Values("In", "NotIn"),
defkit.Field("values", defkit.ParamTypeArray).Of(defkit.ParamTypeString),
)
@@ -165,7 +165,7 @@ var _ = Describe("HelperDefinition", func() {
affinityHelper := defkit.Struct("podAffinityTerm").WithFields(
defkit.Field("labelSelector", defkit.ParamTypeStruct).WithSchemaRef("labelSelector"),
defkit.Field("namespaces", defkit.ParamTypeArray).Of(defkit.ParamTypeString),
defkit.Field("topologyKey", defkit.ParamTypeString).Required(),
defkit.Field("topologyKey", defkit.ParamTypeString).Mandatory(),
)
trait := defkit.NewTrait("schemaref-test").
+122 -15
View File
@@ -20,7 +20,8 @@ package defkit
type baseParam struct {
name string
paramType ParamType
required bool
required bool // when true, emits "!" marker — user must explicitly provide this field in input
mandatory bool // when true, field is non-optional (no ? marker); must have a value but defaults or CUE merging can satisfy it
defaultValue any
description string
forceOptional bool // when true, field stays optional even with a default value
@@ -34,7 +35,8 @@ func (p *baseParam) condition() {}
func (p *baseParam) Name() string { return p.name }
func (p *baseParam) IsRequired() bool { return p.required }
func (p *baseParam) IsOptional() bool { return !p.required }
func (p *baseParam) IsOptional() bool { return !p.required && !p.mandatory }
func (p *baseParam) IsMandatory() bool { return p.mandatory }
func (p *baseParam) HasDefault() bool { return p.defaultValue != nil }
func (p *baseParam) GetDefault() any { return p.defaultValue }
func (p *baseParam) GetDescription() string { return p.description }
@@ -127,14 +129,26 @@ func String(name string) *StringParam {
}
}
// Required marks the parameter as required.
// Required marks the parameter as required in input, emitting the "!" CUE marker.
// The user must explicitly provide this field — even if a default value exists.
func (p *StringParam) Required() *StringParam {
p.required = true
p.mandatory = false
return p
}
// Mandatory marks the parameter as non-optional (no ? marker).
// The field must have a value, but defaults or CUE merging can satisfy it
// without the user explicitly providing it.
func (p *StringParam) Mandatory() *StringParam {
p.mandatory = true
p.required = false
return p
}
// Optional marks the parameter as optional (default behavior).
func (p *StringParam) Optional() *StringParam {
p.mandatory = false
p.required = false
return p
}
@@ -327,14 +341,23 @@ func Int(name string) *IntParam {
}
}
// Required marks the parameter as required.
// Required marks the parameter as required in input, emitting the "!" CUE marker.
func (p *IntParam) Required() *IntParam {
p.required = true
p.mandatory = false
return p
}
// Mandatory marks the parameter as non-optional (no ? marker); must have a value but defaults or CUE merging can satisfy it.
func (p *IntParam) Mandatory() *IntParam {
p.mandatory = true
p.required = false
return p
}
// Optional marks the parameter as optional (default behavior).
func (p *IntParam) Optional() *IntParam {
p.mandatory = false
p.required = false
return p
}
@@ -443,14 +466,23 @@ func Bool(name string) *BoolParam {
}
}
// Required marks the parameter as required.
// Required marks the parameter as required in input, emitting the "!" CUE marker.
func (p *BoolParam) Required() *BoolParam {
p.required = true
p.mandatory = false
return p
}
// Mandatory marks the parameter as non-optional (no ? marker); must have a value but defaults or CUE merging can satisfy it.
func (p *BoolParam) Mandatory() *BoolParam {
p.mandatory = true
p.required = false
return p
}
// Optional marks the parameter as optional (default behavior).
func (p *BoolParam) Optional() *BoolParam {
p.mandatory = false
p.required = false
return p
}
@@ -518,14 +550,23 @@ func Float(name string) *FloatParam {
}
}
// Required marks the parameter as required.
// Required marks the parameter as required in input, emitting the "!" CUE marker.
func (p *FloatParam) Required() *FloatParam {
p.required = true
p.mandatory = false
return p
}
// Mandatory marks the parameter as non-optional (no ? marker); must have a value but defaults or CUE merging can satisfy it.
func (p *FloatParam) Mandatory() *FloatParam {
p.mandatory = true
p.required = false
return p
}
// Optional marks the parameter as optional (default behavior).
func (p *FloatParam) Optional() *FloatParam {
p.mandatory = false
p.required = false
return p
}
@@ -622,14 +663,23 @@ func (p *ArrayParam) Of(elemType ParamType) *ArrayParam {
return p
}
// Required marks the parameter as required.
// Required marks the parameter as required in input, emitting the "!" CUE marker.
func (p *ArrayParam) Required() *ArrayParam {
p.required = true
p.mandatory = false
return p
}
// Mandatory marks the parameter as non-optional (no ? marker); must have a value but defaults or CUE merging can satisfy it.
func (p *ArrayParam) Mandatory() *ArrayParam {
p.mandatory = true
p.required = false
return p
}
// Optional marks the parameter as optional (default behavior).
func (p *ArrayParam) Optional() *ArrayParam {
p.mandatory = false
p.required = false
return p
}
@@ -788,14 +838,23 @@ func (p *MapParam) Of(valueType ParamType) *MapParam {
return p
}
// Required marks the parameter as required.
// Required marks the parameter as required in input, emitting the "!" CUE marker.
func (p *MapParam) Required() *MapParam {
p.required = true
p.mandatory = false
return p
}
// Mandatory marks the parameter as non-optional (no ? marker); must have a value but defaults or CUE merging can satisfy it.
func (p *MapParam) Mandatory() *MapParam {
p.mandatory = true
p.required = false
return p
}
// Optional marks the parameter as optional (default behavior).
func (p *MapParam) Optional() *MapParam {
p.mandatory = false
p.required = false
return p
}
@@ -896,7 +955,8 @@ func (p *MapParam) IsNotEmpty() Condition {
type StructField struct {
name string
fieldType ParamType
required bool
required bool // when true, emits "!" marker — user must explicitly provide this field in input
mandatory bool // when true, field is non-optional (no ? marker); must have a value but defaults or CUE merging can satisfy it
defaultValue any
description string
nested *StructParam // for nested structs
@@ -913,14 +973,22 @@ func Field(name string, fieldType ParamType) *StructField {
}
}
// Required marks the field as required.
// Required marks the field as required in input, emitting the "!" CUE marker.
// The user must explicitly provide this field — even if a default value exists.
func (f *StructField) Required() *StructField {
f.required = true
return f
}
// Mandatory marks the field as non-optional (no ? marker); must have a value but defaults or CUE merging can satisfy it.
func (f *StructField) Mandatory() *StructField {
f.mandatory = true
return f
}
// Optional marks the field as optional (default behavior).
func (f *StructField) Optional() *StructField {
f.mandatory = false
f.required = false
return f
}
@@ -959,9 +1027,12 @@ func (f *StructField) Name() string { return f.name }
// FieldType returns the field type.
func (f *StructField) FieldType() ParamType { return f.fieldType }
// IsRequired returns true if the field is required.
// IsRequired returns true if the field emits the "!" CUE marker — user must explicitly provide it.
func (f *StructField) IsRequired() bool { return f.required }
// IsMandatory returns true if the field is non-optional (no ? marker) but defaults or CUE merging can satisfy it.
func (f *StructField) IsMandatory() bool { return f.mandatory }
// HasDefault returns true if the field has a default value.
func (f *StructField) HasDefault() bool { return f.defaultValue != nil }
@@ -1027,14 +1098,23 @@ func (p *StructParam) WithFields(fields ...*StructField) *StructParam {
return p
}
// Required marks the parameter as required.
// Required marks the parameter as required in input, emitting the "!" CUE marker.
func (p *StructParam) Required() *StructParam {
p.required = true
p.mandatory = false
return p
}
// Mandatory marks the parameter as non-optional (no ? marker); must have a value but defaults or CUE merging can satisfy it.
func (p *StructParam) Mandatory() *StructParam {
p.mandatory = true
p.required = false
return p
}
// Optional marks the parameter as optional (default behavior).
func (p *StructParam) Optional() *StructParam {
p.mandatory = false
p.required = false
return p
}
@@ -1100,14 +1180,23 @@ func (p *EnumParam) Values(values ...string) *EnumParam {
return p
}
// Required marks the parameter as required.
// Required marks the parameter as required in input, emitting the "!" CUE marker.
func (p *EnumParam) Required() *EnumParam {
p.required = true
p.mandatory = false
return p
}
// Mandatory marks the parameter as non-optional (no ? marker); must have a value but defaults or CUE merging can satisfy it.
func (p *EnumParam) Mandatory() *EnumParam {
p.mandatory = true
p.required = false
return p
}
// Optional marks the parameter as optional (default behavior).
func (p *EnumParam) Optional() *EnumParam {
p.mandatory = false
p.required = false
return p
}
@@ -1211,14 +1300,23 @@ func (p *OneOfParam) Default(value string) *OneOfParam {
return p
}
// Required marks the parameter as required.
// Required marks the parameter as required in input, emitting the "!" CUE marker.
func (p *OneOfParam) Required() *OneOfParam {
p.required = true
p.mandatory = false
return p
}
// Mandatory marks the parameter as non-optional (no ? marker); must have a value but defaults or CUE merging can satisfy it.
func (p *OneOfParam) Mandatory() *OneOfParam {
p.mandatory = true
p.required = false
return p
}
// Optional marks the parameter as optional (default behavior).
func (p *OneOfParam) Optional() *OneOfParam {
p.mandatory = false
p.required = false
return p
}
@@ -1289,14 +1387,23 @@ func StringKeyMap(name string) *StringKeyMapParam {
}
}
// Required marks the parameter as required.
// Required marks the parameter as required in input, emitting the "!" CUE marker.
func (p *StringKeyMapParam) Required() *StringKeyMapParam {
p.required = true
p.mandatory = false
return p
}
// Mandatory marks the parameter as non-optional (no ? marker); must have a value but defaults or CUE merging can satisfy it.
func (p *StringKeyMapParam) Mandatory() *StringKeyMapParam {
p.mandatory = true
p.required = false
return p
}
// Optional marks the parameter as optional (default behavior).
func (p *StringKeyMapParam) Optional() *StringKeyMapParam {
p.mandatory = false
p.required = false
return p
}
@@ -608,7 +608,7 @@ var _ = Describe("PatchContainer", func() {
trait := defkit.NewTrait("no-dup-param-test").
Description("Test no duplicate parameter block").
AppliesTo("deployments.apps").
Params(defkit.String("image").Required()).
Params(defkit.String("image").Mandatory()).
Template(func(tpl *defkit.Template) {
tpl.UsePatchContainer(defkit.PatchContainerConfig{
ContainerNameParam: "containerName",
@@ -78,7 +78,7 @@ var _ = Describe("Placement Integration", func() {
Description("A component with placement").
Workload("apps/v1", "Deployment").
RunOn(placement.Label("provider").Eq("aws")).
Params(defkit.String("image").Required())
Params(defkit.String("image").Mandatory())
Expect(c.GetDescription()).To(Equal("A component with placement"))
Expect(c.GetWorkload().Kind()).To(Equal("Deployment"))
@@ -139,7 +139,7 @@ var _ = Describe("Placement Integration", func() {
p := defkit.NewPolicy("full-policy").
Description("A policy with placement").
RunOn(placement.Label("provider").In("aws", "gcp")).
Params(defkit.String("target").Required())
Params(defkit.String("target").Mandatory())
Expect(p.GetDescription()).To(Equal("A policy with placement"))
Expect(p.HasPlacement()).To(BeTrue())
+1 -1
View File
@@ -36,7 +36,7 @@ var _ = Describe("Resource", func() {
Context("Set", func() {
It("should record a Set operation", func() {
image := defkit.String("image").Required()
image := defkit.String("image").Mandatory()
r := defkit.NewResource("apps/v1", "Deployment").
Set("spec.template.spec.containers[0].image", image)
Expect(r.Ops()).To(HaveLen(1))
@@ -133,6 +133,11 @@ var _ = Describe("Parameter Matchers", func() {
p := defkit.String("image")
Expect(p).NotTo(BeRequired())
})
It("should not match a mandatory parameter", func() {
p := defkit.String("image").Mandatory()
Expect(p).NotTo(BeRequired())
})
})
Describe("BeOptional", func() {
@@ -145,6 +150,28 @@ var _ = Describe("Parameter Matchers", func() {
p := defkit.Int("replicas").Required()
Expect(p).NotTo(BeOptional())
})
It("should not match a mandatory parameter", func() {
p := defkit.String("image").Mandatory()
Expect(p).NotTo(BeOptional())
})
})
Describe("BeMandatory", func() {
It("should match a mandatory parameter", func() {
p := defkit.String("image").Mandatory()
Expect(p).To(BeMandatory())
})
It("should not match an optional parameter", func() {
p := defkit.String("image")
Expect(p).NotTo(BeMandatory())
})
It("should not match a required parameter", func() {
p := defkit.String("image").Required()
Expect(p).NotTo(BeMandatory())
})
})
Describe("HaveDefaultValue", func() {
@@ -190,7 +217,7 @@ var _ = Describe("Parameter Matchers", func() {
It("should match when component has parameter", func() {
c := defkit.NewComponent("webservice").
Params(
defkit.String("image").Required(),
defkit.String("image").Mandatory(),
defkit.Int("replicas").Default(1),
)
Expect(c).To(HaveParamNamed("image"))
@@ -24,12 +24,39 @@ import (
"github.com/oam-dev/kubevela/pkg/definition/defkit"
)
// paramAccessor provides common access to parameter properties.
type paramAccessor interface {
// named is the minimal interface for extracting a parameter name (used in failure messages).
type named interface {
Name() string
}
// requiredParam is satisfied by any parameter that can report required status.
type requiredParam interface {
named
IsRequired() bool
}
// optionalParam is satisfied by any parameter that can report optional status.
type optionalParam interface {
named
IsOptional() bool
}
// mandatoryParam is satisfied by any parameter that can report mandatory status.
type mandatoryParam interface {
named
IsMandatory() bool
}
// defaultParam is satisfied by any parameter that can report its default value.
type defaultParam interface {
named
HasDefault() bool
GetDefault() any
}
// describedParam is satisfied by any parameter that can report its description.
type describedParam interface {
named
GetDescription() string
}
@@ -41,7 +68,7 @@ func BeRequired() types.GomegaMatcher {
type requiredMatcher struct{}
func (m *requiredMatcher) Match(actual interface{}) (bool, error) {
param, ok := actual.(paramAccessor)
param, ok := actual.(requiredParam)
if !ok {
return false, fmt.Errorf("BeRequired expects a parameter type, got %T", actual)
}
@@ -49,12 +76,12 @@ func (m *requiredMatcher) Match(actual interface{}) (bool, error) {
}
func (m *requiredMatcher) FailureMessage(actual interface{}) string {
param := actual.(paramAccessor)
param := actual.(requiredParam)
return fmt.Sprintf("Expected parameter %q to be required", param.Name())
}
func (m *requiredMatcher) NegatedFailureMessage(actual interface{}) string {
param := actual.(paramAccessor)
param := actual.(requiredParam)
return fmt.Sprintf("Expected parameter %q not to be required", param.Name())
}
@@ -66,23 +93,48 @@ func BeOptional() types.GomegaMatcher {
type optionalMatcher struct{}
func (m *optionalMatcher) Match(actual interface{}) (bool, error) {
param, ok := actual.(paramAccessor)
param, ok := actual.(optionalParam)
if !ok {
return false, fmt.Errorf("BeOptional expects a parameter type, got %T", actual)
}
return !param.IsRequired(), nil
return param.IsOptional(), nil
}
func (m *optionalMatcher) FailureMessage(actual interface{}) string {
param := actual.(paramAccessor)
param := actual.(optionalParam)
return fmt.Sprintf("Expected parameter %q to be optional", param.Name())
}
func (m *optionalMatcher) NegatedFailureMessage(actual interface{}) string {
param := actual.(paramAccessor)
param := actual.(optionalParam)
return fmt.Sprintf("Expected parameter %q not to be optional", param.Name())
}
// BeMandatory returns a matcher that checks if a parameter is mandatory (non-optional, no ? marker).
func BeMandatory() types.GomegaMatcher {
return &mandatoryMatcher{}
}
type mandatoryMatcher struct{}
func (m *mandatoryMatcher) Match(actual interface{}) (bool, error) {
param, ok := actual.(mandatoryParam)
if !ok {
return false, fmt.Errorf("BeMandatory expects a parameter type, got %T", actual)
}
return param.IsMandatory(), nil
}
func (m *mandatoryMatcher) FailureMessage(actual interface{}) string {
param := actual.(mandatoryParam)
return fmt.Sprintf("Expected parameter %q to be mandatory", param.Name())
}
func (m *mandatoryMatcher) NegatedFailureMessage(actual interface{}) string {
param := actual.(mandatoryParam)
return fmt.Sprintf("Expected parameter %q not to be mandatory", param.Name())
}
// HaveDefaultValue returns a matcher that checks if a parameter has the expected default value.
func HaveDefaultValue(expected any) types.GomegaMatcher {
return &defaultValueMatcher{expectedValue: expected}
@@ -93,7 +145,7 @@ type defaultValueMatcher struct {
}
func (m *defaultValueMatcher) Match(actual interface{}) (bool, error) {
param, ok := actual.(paramAccessor)
param, ok := actual.(defaultParam)
if !ok {
return false, fmt.Errorf("HaveDefaultValue expects a parameter type, got %T", actual)
}
@@ -104,7 +156,7 @@ func (m *defaultValueMatcher) Match(actual interface{}) (bool, error) {
}
func (m *defaultValueMatcher) FailureMessage(actual interface{}) string {
param := actual.(paramAccessor)
param := actual.(defaultParam)
if !param.HasDefault() {
return fmt.Sprintf("Expected parameter %q to have default value %v, but it has no default", param.Name(), m.expectedValue)
}
@@ -112,7 +164,7 @@ func (m *defaultValueMatcher) FailureMessage(actual interface{}) string {
}
func (m *defaultValueMatcher) NegatedFailureMessage(actual interface{}) string {
param := actual.(paramAccessor)
param := actual.(defaultParam)
return fmt.Sprintf("Expected parameter %q not to have default value %v", param.Name(), m.expectedValue)
}
@@ -126,7 +178,7 @@ type descriptionMatcher struct {
}
func (m *descriptionMatcher) Match(actual interface{}) (bool, error) {
param, ok := actual.(paramAccessor)
param, ok := actual.(describedParam)
if !ok {
return false, fmt.Errorf("HaveDescription expects a parameter type, got %T", actual)
}
@@ -134,12 +186,12 @@ func (m *descriptionMatcher) Match(actual interface{}) (bool, error) {
}
func (m *descriptionMatcher) FailureMessage(actual interface{}) string {
param := actual.(paramAccessor)
param := actual.(describedParam)
return fmt.Sprintf("Expected parameter %q to have description %q, but got %q", param.Name(), m.expectedDesc, param.GetDescription())
}
func (m *descriptionMatcher) NegatedFailureMessage(actual interface{}) string {
param := actual.(paramAccessor)
param := actual.(describedParam)
return fmt.Sprintf("Expected parameter %q not to have description %q", param.Name(), m.expectedDesc)
}
+18 -18
View File
@@ -180,7 +180,7 @@ parameter: #PatchParams
trait := defkit.NewTrait("scaler").
Description("Scale workloads").
AppliesTo("deployments.apps").
Params(defkit.Int("replicas").Default(1).Required().Description("Number of replicas"))
Params(defkit.Int("replicas").Default(1).Mandatory().Description("Number of replicas"))
cue := trait.ToCue()
@@ -402,7 +402,7 @@ template: {
AppliesTo("deployments.apps").
Params(
defkit.Int("replicas").Default(1).Description("Number of replicas"),
defkit.String("image").Required().Description("Container image"),
defkit.String("image").Mandatory().Description("Container image"),
)
cue := trait.ToCue()
@@ -560,7 +560,7 @@ template: {
It("should generate Optional field guards in Map comprehension", func() {
items := defkit.Array("items").WithFields(
defkit.String("name").Required(),
defkit.String("name").Mandatory(),
defkit.String("label"),
defkit.Int("priority"),
)
@@ -595,10 +595,10 @@ template: {
It("should generate If/EndIf with SetIf using sub-field conditions", func() {
parent := defkit.Map("parent").WithFields(
defkit.Array("required").WithFields(
defkit.String("key").Required(),
defkit.String("key").Mandatory(),
),
defkit.Array("preferred").WithFields(
defkit.Int("weight").Required(),
defkit.Int("weight").Mandatory(),
),
)
trait := defkit.NewTrait("if-subfield-test").
@@ -701,9 +701,9 @@ template: {
Context("PatchKey with ArrayParam (no array wrapping)", func() {
It("should emit direct assignment when single element is an ArrayParam", func() {
items := defkit.Array("items").WithFields(
defkit.String("name").Required(),
defkit.String("value").Required(),
).Required()
defkit.String("name").Mandatory(),
defkit.String("value").Mandatory(),
).Mandatory()
trait := defkit.NewTrait("patchkey-array-test").
Description("Test PatchKey with ArrayParam").
@@ -837,7 +837,7 @@ template: {
})
It("should emit patchStrategy annotation in CUE output for unconditional field", func() {
strategy := defkit.Struct("strategy").Required().WithFields(
strategy := defkit.Struct("strategy").Mandatory().WithFields(
defkit.Field("type", defkit.ParamTypeString).Default("RollingUpdate"),
)
@@ -861,7 +861,7 @@ template: {
It("should emit patchStrategy annotation inside conditional block", func() {
kind := defkit.String("kind").Default("Deployment").Values("Deployment", "StatefulSet")
strategy := defkit.Struct("strategy").Required().WithFields(
strategy := defkit.Struct("strategy").Mandatory().WithFields(
defkit.Field("type", defkit.ParamTypeString).Default("RollingUpdate"),
)
@@ -988,7 +988,7 @@ template: {
It("should produce correct k8s-update-strategy-like pattern", func() {
targetKind := defkit.String("targetKind").Default("Deployment").Values("Deployment", "StatefulSet", "DaemonSet")
strategy := defkit.Struct("strategy").Required().WithFields(
strategy := defkit.Struct("strategy").Mandatory().WithFields(
defkit.Field("type", defkit.ParamTypeString).Default("RollingUpdate").Values("RollingUpdate", "Recreate", "OnDelete"),
defkit.Field("rollingStrategy", defkit.ParamTypeStruct).
Nested(defkit.Struct("rollingStrategy").WithFields(
@@ -1069,7 +1069,7 @@ template: {
Helper("Handler", defkit.Struct("Handler").WithFields(
defkit.Field("exec", defkit.ParamTypeStruct).
Nested(defkit.Struct("exec").WithFields(
defkit.Field("command", defkit.ParamTypeArray).Of(defkit.ParamTypeString).Required(),
defkit.Field("command", defkit.ParamTypeArray).Of(defkit.ParamTypeString).Mandatory(),
)),
)).
Template(func(tpl *defkit.Template) {
@@ -1201,7 +1201,7 @@ template: {
})
It("should render unconditional SpreadAll with simple value", func() {
image := defkit.String("image").Required()
image := defkit.String("image").Mandatory()
trait := defkit.NewTrait("spreadall-simple-test").
Description("Test SpreadAll with simple value").
@@ -1243,7 +1243,7 @@ template: {
It("should render SpreadAll inside an IfBlock", func() {
enabled := defkit.Bool("enabled").Default(false)
image := defkit.String("image").Required()
image := defkit.String("image").Mandatory()
trait := defkit.NewTrait("spreadall-ifblock-test").
Description("Test SpreadAll inside IfBlock").
@@ -1338,8 +1338,8 @@ template: {
Helper("Config", defkit.Struct("Config").WithFields(
defkit.Field("headers", defkit.ParamTypeArray).
Nested(defkit.Struct("headers").WithFields(
defkit.Field("name", defkit.ParamTypeString).Required(),
defkit.Field("value", defkit.ParamTypeString).Required(),
defkit.Field("name", defkit.ParamTypeString).Mandatory(),
defkit.Field("value", defkit.ParamTypeString).Mandatory(),
)),
)).
Template(func(tpl *defkit.Template) {
@@ -1360,7 +1360,7 @@ template: {
AppliesTo("deployments.apps").
Helper("Port", defkit.Int("Port").Min(1).Max(65535)).
Helper("Endpoint", defkit.Struct("Endpoint").WithFields(
defkit.Field("port", defkit.ParamTypeInt).WithSchemaRef("Port").Required(),
defkit.Field("port", defkit.ParamTypeInt).WithSchemaRef("Port").Mandatory(),
defkit.Field("host", defkit.ParamTypeString),
)).
Template(func(tpl *defkit.Template) {
@@ -1371,7 +1371,7 @@ template: {
Expect(cue).To(ContainSubstring("#Port: int & >=1 & <=65535"))
Expect(cue).To(ContainSubstring("#Endpoint: {"))
Expect(cue).To(ContainSubstring("port: #Port"))
Expect(cue).To(MatchRegexp(`port:\s+#Port`))
Expect(cue).To(ContainSubstring("host?: string"))
})
})
+1 -1
View File
@@ -163,7 +163,7 @@ func TestFromTyped_ChainAdditionalOperations(t *testing.T) {
}
// Chain additional operations
image := defkit.String("image").Required()
image := defkit.String("image").Mandatory()
r.Set("spec.template.spec.containers[0].image", image)
ops := r.Ops()
+4 -2
View File
@@ -57,10 +57,12 @@ type Param interface {
Value
// Name returns the parameter name
Name() string
// IsRequired returns true if the parameter is required
// IsRequired returns true if the parameter emits the "!" CUE marker — user must explicitly provide it
IsRequired() bool
// IsOptional returns true if the parameter is optional
// IsOptional returns true if the parameter is optional (emits "?" marker)
IsOptional() bool
// IsMandatory returns true if the parameter is non-optional (no ? marker) but defaults or CUE merging can satisfy it
IsMandatory() bool
// HasDefault returns true if the parameter has a default value
HasDefault() bool
// GetDefault returns the default value, or nil if none
+1 -1
View File
@@ -331,7 +331,7 @@ template: {
step := defkit.NewWorkflowStep("check-metrics").
Description("Verify metrics").
WithImports("vela/metrics").
Params(defkit.String("query").Required()).
Params(defkit.String("query").Mandatory()).
TemplateBody("check: metrics.#PromCheck & {\n\t$params: query: parameter.query\n}")
cue := step.ToCue()