Files
Jerrin FrancisandGitHub c6ee19ee45 Fix: remove Mandatory() API and make non-optional the default field state in defkit (#7070)
Replace the three-state field marker system (Optional/Mandatory/Required)
  with a simpler two-state system where the default behavior produces
  `field: type` (no marker), `.Required()` produces `field!: type`, and
  `.Optional()` produces `field?: type`.

  - Remove Mandatory(), IsMandatory(), ForceOptional(), IsForceOptional()
    from all param types, StructField, Param interface, and CUE generator
  - Replace `mandatory` and `forceOptional` fields with `optional` field
    in baseParam and StructField
  - Simplify CUE marker resolution to: default=none, Required="!",
    Optional="?"
  - Remove BeMandatory() matcher and mandatoryParam interface
  - Migrate all test usages: .Mandatory() removed (now default behavior),
    .ForceOptional() replaced with .Optional()

Signed-off-by: Jerrin Francis <jerrinfrancis7@gmail.com>
2026-03-16 06:38:06 -07:00

1133 lines
36 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_test
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/oam-dev/kubevela/pkg/definition/defkit"
)
var _ = Describe("Collections", func() {
Context("From", func() {
It("should create a collection operation (alias for Each)", func() {
ports := defkit.List("ports")
col := defkit.From(ports)
Expect(col).NotTo(BeNil())
Expect(col.Source()).To(Equal(ports))
})
})
Context("F", func() {
It("should create a FieldRef (alias for FieldRef)", func() {
ref := defkit.F("port")
Expect(ref).To(Equal(defkit.FieldRef("port")))
})
})
Context("Each", func() {
It("should create a collection operation from a list parameter", func() {
ports := defkit.List("ports")
col := defkit.Each(ports)
Expect(col).NotTo(BeNil())
Expect(col.Source()).To(Equal(ports))
})
It("should chain Filter operation", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).Filter(defkit.FieldEquals("expose", true))
Expect(col.Operations()).To(HaveLen(1))
})
It("should chain Map operation", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).Map(defkit.FieldMap{
"containerPort": defkit.FieldRef("port"),
})
Expect(col.Operations()).To(HaveLen(1))
})
It("should chain multiple operations", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).
Filter(defkit.FieldEquals("expose", true)).
Map(defkit.FieldMap{
"port": defkit.FieldRef("port"),
"targetPort": defkit.FieldRef("port"),
}).
Pick("port", "targetPort")
Expect(col.Operations()).To(HaveLen(3))
})
It("should chain Wrap operation", func() {
secrets := defkit.StringList("imagePullSecrets")
col := defkit.Each(secrets).Wrap("name")
Expect(col.Operations()).To(HaveLen(1))
})
It("should chain MapVariant operation", func() {
volumes := defkit.List("volumes")
col := defkit.Each(volumes).
Map(defkit.FieldMap{"name": defkit.FieldRef("name")}).
MapVariant("type", "pvc", defkit.FieldMap{
"persistentVolumeClaim.claimName": defkit.FieldRef("claimName"),
}).
MapVariant("type", "emptyDir", defkit.FieldMap{
"emptyDir.medium": defkit.FieldRef("medium"),
})
Expect(col.Operations()).To(HaveLen(3))
})
It("should chain Rename operation", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).Rename("port", "containerPort")
Expect(col.Operations()).To(HaveLen(1))
})
It("should chain Flatten operation", func() {
volumes := defkit.List("volumes")
col := defkit.Each(volumes).Flatten()
Expect(col.Operations()).To(HaveLen(1))
})
It("should chain DefaultField operation", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).DefaultField("name", defkit.Format("port-%v", defkit.FieldRef("port")))
Expect(col.Operations()).To(HaveLen(1))
})
It("should chain Dedupe operation on CollectionOp", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).Dedupe("name")
Expect(col.Operations()).To(HaveLen(1))
})
})
Context("FieldRef", func() {
It("should store field name and resolve correctly from item", func() {
ref := defkit.FieldRef("port")
Expect(string(ref)).To(Equal("port"))
// Verify it resolves correctly through a collection
ports := defkit.List("ports")
col := defkit.Each(ports).Map(defkit.FieldMap{"p": ref})
items := []any{map[string]any{"port": 8080}}
results := col.Collect(items)
Expect(results).To(HaveLen(1))
Expect(results[0]["p"]).To(Equal(8080))
})
It("should support Or fallback and use fallback when primary is nil", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).Map(defkit.FieldMap{
"display": defkit.FieldRef("name").Or(defkit.Format("port-%v", defkit.FieldRef("port"))),
})
// Primary field present — should use primary
items := []any{map[string]any{"port": 80, "name": "http"}}
results := col.Collect(items)
Expect(results[0]["display"]).To(Equal("http"))
// Primary field missing — should use fallback
items = []any{map[string]any{"port": 443}}
results = col.Collect(items)
Expect(results[0]["display"]).To(Equal("port-443"))
})
})
Context("FieldEquals", func() {
It("should filter items matching the field equality predicate", func() {
pred := defkit.FieldEquals("expose", true)
ports := defkit.List("ports")
col := defkit.Each(ports).Filter(pred)
items := []any{
map[string]any{"port": 80, "expose": true},
map[string]any{"port": 443, "expose": false},
}
results := col.Collect(items)
Expect(results).To(HaveLen(1))
Expect(results[0]["port"]).To(Equal(80))
})
})
Context("FieldExists", func() {
It("should filter items where field is present", func() {
pred := defkit.FieldExists("items")
ports := defkit.List("data")
col := defkit.Each(ports).Filter(pred)
items := []any{
map[string]any{"name": "a", "items": []string{"x"}},
map[string]any{"name": "b"},
}
results := col.Collect(items)
Expect(results).To(HaveLen(1))
Expect(results[0]["name"]).To(Equal("a"))
})
})
Context("Format", func() {
It("should create format field value with required imports", func() {
f := defkit.Format("port-%v", defkit.FieldRef("port"))
Expect(f).NotTo(BeNil())
Expect(f.RequiredImports()).To(ContainElement("strconv"))
})
})
Context("LitField", func() {
It("should resolve literal field value from item", func() {
lit := defkit.LitField("TCP")
Expect(lit).NotTo(BeNil())
// LitField should resolve to the literal value regardless of item content
ports := defkit.List("ports")
col := defkit.Each(ports).Map(defkit.FieldMap{"protocol": lit})
items := []any{map[string]any{"port": 80}}
results := col.Collect(items)
Expect(results).To(HaveLen(1))
Expect(results[0]["protocol"]).To(Equal("TCP"))
})
})
Context("FromFields", func() {
It("should create multi-source collection", func() {
volumeMounts := defkit.Object("volumeMounts")
ms := defkit.FromFields(volumeMounts, "pvc", "configMap", "secret")
Expect(ms).NotTo(BeNil())
Expect(ms.Source()).To(Equal(volumeMounts))
Expect(ms.Sources()).To(Equal([]string{"pvc", "configMap", "secret"}))
})
It("should chain Pick operation", func() {
volumeMounts := defkit.Object("volumeMounts")
ms := defkit.FromFields(volumeMounts, "pvc", "configMap").
Pick("name", "mountPath")
Expect(ms.Operations()).To(HaveLen(1))
})
It("should chain Dedupe operation", func() {
volumeMounts := defkit.Object("volumeMounts")
ms := defkit.FromFields(volumeMounts, "pvc", "configMap").
Dedupe("name")
Expect(ms.Operations()).To(HaveLen(1))
})
It("should chain MapBySource operation", func() {
volumeMounts := defkit.Object("volumeMounts")
ms := defkit.FromFields(volumeMounts, "pvc", "configMap").
MapBySource(map[string]defkit.FieldMap{
"pvc": {
"name": defkit.FieldRef("name"),
"persistentVolumeClaim": defkit.Nested(defkit.FieldMap{"claimName": defkit.FieldRef("claimName")}),
},
"configMap": {
"name": defkit.FieldRef("name"),
"configMap": defkit.Nested(defkit.FieldMap{"name": defkit.FieldRef("cmName")}),
},
})
Expect(ms.MapBySourceMappings()).To(HaveLen(2))
})
})
Context("Nested", func() {
It("should resolve nested field mapping from item", func() {
nested := defkit.Nested(defkit.FieldMap{
"claimName": defkit.FieldRef("claimName"),
})
Expect(nested).NotTo(BeNil())
// Verify nested resolves correctly through a collection
vols := defkit.List("volumes")
col := defkit.Each(vols).Map(defkit.FieldMap{
"pvc": nested,
})
items := []any{map[string]any{"claimName": "my-pvc"}}
results := col.Collect(items)
Expect(results).To(HaveLen(1))
Expect(results[0]["pvc"]).To(Equal(map[string]any{"claimName": "my-pvc"}))
})
})
Context("Optional and OptionalFieldRef", func() {
It("should include optional field when present and omit when absent", func() {
vols := defkit.List("volumes")
col := defkit.Each(vols).Map(defkit.FieldMap{
"name": defkit.FieldRef("name"),
"items": defkit.Optional("items"),
})
items := []any{
map[string]any{"name": "vol1", "items": []string{"a"}},
map[string]any{"name": "vol2"},
}
results := col.Collect(items)
Expect(results).To(HaveLen(2))
Expect(results[0]).To(HaveKey("items"))
Expect(results[1]).NotTo(HaveKey("items"))
})
It("should create optional field reference via OptionalFieldRef alias", func() {
vols := defkit.List("volumes")
col := defkit.Each(vols).Map(defkit.FieldMap{
"name": defkit.FieldRef("name"),
"subPath": defkit.OptionalFieldRef("subPath"),
})
items := []any{
map[string]any{"name": "vol1", "subPath": "/data"},
map[string]any{"name": "vol2"},
}
results := col.Collect(items)
Expect(results).To(HaveLen(2))
Expect(results[0]["subPath"]).To(Equal("/data"))
Expect(results[1]).NotTo(HaveKey("subPath"))
})
})
Context("CompoundOptionalField", func() {
It("should create compound optional field with OptionalFieldWithCond", func() {
cond := defkit.Eq(defkit.String("exposeType"), defkit.Lit("NodePort"))
compOpt := defkit.OptionalFieldWithCond("nodePort", cond)
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"),
defkit.Int("nodePort"),
)
col := defkit.Each(ports).Map(defkit.FieldMap{
"port": defkit.FieldRef("port"),
"nodePort": compOpt,
})
Expect(col.Operations()).To(HaveLen(1))
})
It("should be usable as a FieldValue in FieldMap alongside regular fields", func() {
cond := defkit.Eq(defkit.String("exposeType"), defkit.Lit("NodePort"))
fm := defkit.FieldMap{
"port": defkit.FieldRef("port"),
"nodePort": defkit.OptionalFieldWithCond("nodePort", cond),
}
Expect(fm).To(HaveLen(2))
Expect(fm).To(HaveKey("port"))
Expect(fm).To(HaveKey("nodePort"))
})
})
Context("NestedFieldMap", func() {
It("should resolve nested field mapping identically to Nested", func() {
nested := defkit.NestedFieldMap(defkit.FieldMap{
"claimName": defkit.FieldRef("claimName"),
})
Expect(nested).NotTo(BeNil())
vols := defkit.List("volumes")
col := defkit.Each(vols).Map(defkit.FieldMap{"pvc": nested})
items := []any{map[string]any{"claimName": "my-pvc"}}
results := col.Collect(items)
Expect(results).To(HaveLen(1))
Expect(results[0]["pvc"]).To(Equal(map[string]any{"claimName": "my-pvc"}))
})
})
Context("ConcatExpr", func() {
It("should create concatenation expression from struct array helper", func() {
tpl := defkit.NewTemplate()
volumeMounts := defkit.Object("volumeMounts")
structHelper := tpl.StructArrayHelper("mountsArray", volumeMounts).
Field("pvc", defkit.FieldMap{"name": defkit.FieldRef("name")}).
Field("configMap", defkit.FieldMap{"name": defkit.FieldRef("name")}).
Build()
concatExpr := defkit.ConcatExpr(structHelper, "pvc", "configMap")
Expect(concatExpr).NotTo(BeNil())
Expect(concatExpr.Source()).To(Equal(structHelper))
Expect(concatExpr.Fields()).To(Equal([]string{"pvc", "configMap"}))
})
})
Context("Collection Resolution", func() {
var (
ports *defkit.ArrayParam
comp *defkit.ComponentDefinition
)
BeforeEach(func() {
ports = defkit.List("ports").WithFields(
defkit.Int("port"),
defkit.String("name"),
defkit.String("protocol").Default("TCP"),
defkit.Bool("expose").Default(false),
)
})
It("should resolve Each().Map() transformation", func() {
comp = defkit.NewComponent("test").
Params(ports).
Template(func(tpl *defkit.Template) {
containerPorts := defkit.Each(ports).Map(defkit.FieldMap{
"containerPort": defkit.FieldRef("port"),
"name": defkit.FieldRef("name"),
"protocol": defkit.FieldRef("protocol"),
})
tpl.Output(
defkit.NewResource("apps/v1", "Deployment").
Set("spec.template.spec.containers[0].ports", containerPorts),
)
})
rendered := comp.Render(
defkit.TestContext().
WithParam("ports", []map[string]any{
{"port": 80, "name": "http", "protocol": "TCP", "expose": true},
{"port": 443, "name": "https", "protocol": "TCP", "expose": false},
}),
)
containerPorts := rendered.Get("spec.template.spec.containers[0].ports").([]any)
Expect(containerPorts).To(HaveLen(2))
Expect(containerPorts[0].(map[string]any)["containerPort"]).To(Equal(80))
Expect(containerPorts[0].(map[string]any)["name"]).To(Equal("http"))
Expect(containerPorts[1].(map[string]any)["containerPort"]).To(Equal(443))
})
It("should resolve Each().Filter().Map() transformation", func() {
comp = defkit.NewComponent("test").
Params(ports).
Template(func(tpl *defkit.Template) {
servicePorts := defkit.Each(ports).
Filter(defkit.FieldEquals("expose", true)).
Map(defkit.FieldMap{
"port": defkit.FieldRef("port"),
"targetPort": defkit.FieldRef("port"),
"name": defkit.FieldRef("name"),
})
tpl.Output(
defkit.NewResource("v1", "Service").
Set("spec.ports", servicePorts),
)
})
rendered := comp.Render(
defkit.TestContext().
WithParam("ports", []map[string]any{
{"port": 80, "name": "http", "protocol": "TCP", "expose": true},
{"port": 443, "name": "https", "protocol": "TCP", "expose": false},
{"port": 8080, "name": "admin", "protocol": "TCP", "expose": true},
}),
)
servicePorts := rendered.Get("spec.ports").([]any)
Expect(servicePorts).To(HaveLen(2)) // Only expose=true ports
Expect(servicePorts[0].(map[string]any)["port"]).To(Equal(80))
Expect(servicePorts[1].(map[string]any)["port"]).To(Equal(8080))
})
It("should resolve Each().Wrap() transformation", func() {
secrets := defkit.StringList("imagePullSecrets")
comp = defkit.NewComponent("test").
Params(secrets).
Template(func(tpl *defkit.Template) {
pullSecrets := defkit.Each(secrets).Wrap("name")
tpl.Output(
defkit.NewResource("apps/v1", "Deployment").
Set("spec.template.spec.imagePullSecrets", pullSecrets),
)
})
rendered := comp.Render(
defkit.TestContext().
WithParam("imagePullSecrets", []any{"docker-secret", "gcr-secret"}),
)
pullSecrets := rendered.Get("spec.template.spec.imagePullSecrets").([]any)
Expect(pullSecrets).To(HaveLen(2))
Expect(pullSecrets[0].(map[string]any)["name"]).To(Equal("docker-secret"))
Expect(pullSecrets[1].(map[string]any)["name"]).To(Equal("gcr-secret"))
})
})
Context("MultiSource Resolution", func() {
var (
volumeMounts *defkit.MapParam
comp *defkit.ComponentDefinition
)
BeforeEach(func() {
volumeMounts = defkit.Object("volumeMounts")
})
It("should resolve FromFields().Pick().Dedupe() for container mounts", func() {
comp = defkit.NewComponent("test").
Params(volumeMounts).
Template(func(tpl *defkit.Template) {
containerMounts := defkit.FromFields(volumeMounts, "pvc", "configMap", "secret").
Pick("name", "mountPath", "subPath").
Dedupe("name")
tpl.Output(
defkit.NewResource("apps/v1", "Deployment").
Set("spec.template.spec.containers[0].volumeMounts", containerMounts),
)
})
rendered := comp.Render(
defkit.TestContext().
WithParam("volumeMounts", map[string]any{
"pvc": []map[string]any{
{"name": "data", "mountPath": "/data", "claimName": "data-pvc"},
},
"configMap": []map[string]any{
{"name": "config", "mountPath": "/etc/config", "cmName": "app-config"},
},
"secret": []map[string]any{
{"name": "creds", "mountPath": "/etc/creds", "secretName": "app-secret"},
},
}),
)
mounts := rendered.Get("spec.template.spec.containers[0].volumeMounts").([]any)
Expect(mounts).To(HaveLen(3))
// Verify Pick only included specified fields
Expect(mounts[0].(map[string]any)).To(HaveKey("name"))
Expect(mounts[0].(map[string]any)).To(HaveKey("mountPath"))
Expect(mounts[0].(map[string]any)).NotTo(HaveKey("claimName"))
})
It("should resolve FromFields().MapBySource() for pod volumes", func() {
comp = defkit.NewComponent("test").
Params(volumeMounts).
Template(func(tpl *defkit.Template) {
podVolumes := defkit.FromFields(volumeMounts, "pvc", "configMap", "emptyDir").
MapBySource(map[string]defkit.FieldMap{
"pvc": {
"name": defkit.FieldRef("name"),
"persistentVolumeClaim": defkit.Nested(defkit.FieldMap{"claimName": defkit.FieldRef("claimName")}),
},
"configMap": {
"name": defkit.FieldRef("name"),
"configMap": defkit.Nested(defkit.FieldMap{
"name": defkit.FieldRef("cmName"),
"defaultMode": defkit.FieldRef("defaultMode"),
}),
},
"emptyDir": {
"name": defkit.FieldRef("name"),
"emptyDir": defkit.Nested(defkit.FieldMap{"medium": defkit.FieldRef("medium")}),
},
}).
Dedupe("name")
tpl.Output(
defkit.NewResource("apps/v1", "Deployment").
Set("spec.template.spec.volumes", podVolumes),
)
})
rendered := comp.Render(
defkit.TestContext().
WithParam("volumeMounts", map[string]any{
"pvc": []map[string]any{
{"name": "data", "mountPath": "/data", "claimName": "data-pvc"},
},
"configMap": []map[string]any{
{"name": "config", "mountPath": "/etc/config", "cmName": "app-config", "defaultMode": 420},
},
"emptyDir": []map[string]any{
{"name": "cache", "mountPath": "/cache", "medium": "Memory"},
},
}),
)
volumes := rendered.Get("spec.template.spec.volumes").([]any)
Expect(volumes).To(HaveLen(3))
// Verify PVC volume structure
pvcVol := volumes[0].(map[string]any)
Expect(pvcVol["name"]).To(Equal("data"))
Expect(pvcVol["persistentVolumeClaim"].(map[string]any)["claimName"]).To(Equal("data-pvc"))
// Verify ConfigMap volume structure
cmVol := volumes[1].(map[string]any)
Expect(cmVol["name"]).To(Equal("config"))
Expect(cmVol["configMap"].(map[string]any)["name"]).To(Equal("app-config"))
Expect(cmVol["configMap"].(map[string]any)["defaultMode"]).To(Equal(420))
// Verify EmptyDir volume structure
emptyVol := volumes[2].(map[string]any)
Expect(emptyVol["name"]).To(Equal("cache"))
Expect(emptyVol["emptyDir"].(map[string]any)["medium"]).To(Equal("Memory"))
})
It("should dedupe volumes by name", func() {
comp = defkit.NewComponent("test").
Params(volumeMounts).
Template(func(tpl *defkit.Template) {
podVolumes := defkit.FromFields(volumeMounts, "pvc", "configMap").
MapBySource(map[string]defkit.FieldMap{
"pvc": {
"name": defkit.FieldRef("name"),
"persistentVolumeClaim": defkit.Nested(defkit.FieldMap{"claimName": defkit.FieldRef("claimName")}),
},
"configMap": {
"name": defkit.FieldRef("name"),
"configMap": defkit.Nested(defkit.FieldMap{"name": defkit.FieldRef("cmName")}),
},
}).
Dedupe("name")
tpl.Output(
defkit.NewResource("apps/v1", "Deployment").
Set("spec.template.spec.volumes", podVolumes),
)
})
rendered := comp.Render(
defkit.TestContext().
WithParam("volumeMounts", map[string]any{
"pvc": []map[string]any{
{"name": "shared", "claimName": "pvc-1"},
{"name": "data", "claimName": "pvc-2"},
},
"configMap": []map[string]any{
{"name": "shared", "cmName": "cm-1"}, // Duplicate name - should be deduped
{"name": "config", "cmName": "cm-2"},
},
}),
)
volumes := rendered.Get("spec.template.spec.volumes").([]any)
Expect(volumes).To(HaveLen(3)) // "shared" appears only once (from pvc, first occurrence wins)
names := make([]string, len(volumes))
for i, v := range volumes {
names[i] = v.(map[string]any)["name"].(string)
}
Expect(names).To(ConsistOf("shared", "data", "config"))
})
It("should handle Optional fields in Nested mappings", func() {
comp = defkit.NewComponent("test").
Params(volumeMounts).
Template(func(tpl *defkit.Template) {
podVolumes := defkit.FromFields(volumeMounts, "configMap").
MapBySource(map[string]defkit.FieldMap{
"configMap": {
"name": defkit.FieldRef("name"),
"configMap": defkit.Nested(defkit.FieldMap{
"name": defkit.FieldRef("cmName"),
"defaultMode": defkit.FieldRef("defaultMode"),
"items": defkit.Optional("items"), // Optional field
}),
},
})
tpl.Output(
defkit.NewResource("apps/v1", "Deployment").
Set("spec.template.spec.volumes", podVolumes),
)
})
// Test with items present
rendered := comp.Render(
defkit.TestContext().
WithParam("volumeMounts", map[string]any{
"configMap": []map[string]any{
{
"name": "with-items",
"cmName": "cm-1",
"defaultMode": 420,
"items": []map[string]any{{"key": "app.conf", "path": "app.conf"}},
},
{
"name": "without-items",
"cmName": "cm-2",
"defaultMode": 420,
// No items field
},
},
}),
)
volumes := rendered.Get("spec.template.spec.volumes").([]any)
Expect(volumes).To(HaveLen(2))
// Volume with items should have items field
volWithItems := volumes[0].(map[string]any)
Expect(volWithItems["configMap"].(map[string]any)).To(HaveKey("items"))
// Volume without items should not have items field
volWithoutItems := volumes[1].(map[string]any)
Expect(volWithoutItems["configMap"].(map[string]any)).NotTo(HaveKey("items"))
})
})
// --- Go 1.23 Iterator Methods Tests ---
Context("Go 1.23 Iterator Methods", func() {
Context("CollectionOp.All", func() {
It("should iterate over all items using iter.Seq", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).
Filter(defkit.FieldEquals("expose", true))
items := []any{
map[string]any{"port": 80, "expose": true},
map[string]any{"port": 443, "expose": false},
map[string]any{"port": 8080, "expose": true},
}
var results []map[string]any
for item := range col.All(items) {
results = append(results, item)
}
Expect(results).To(HaveLen(2))
Expect(results[0]["port"]).To(Equal(80))
Expect(results[1]["port"]).To(Equal(8080))
})
It("should support early termination", func() {
ports := defkit.List("ports")
col := defkit.Each(ports)
items := []any{
map[string]any{"port": 80},
map[string]any{"port": 443},
map[string]any{"port": 8080},
}
count := 0
for range col.All(items) {
count++
if count == 2 {
break
}
}
Expect(count).To(Equal(2))
})
})
Context("CollectionOp.AllPairs", func() {
It("should iterate with index and item using iter.Seq2", func() {
ports := defkit.List("ports")
col := defkit.Each(ports)
items := []any{
map[string]any{"port": 80},
map[string]any{"port": 443},
}
var indices []int
var ports_list []int
for i, item := range col.AllPairs(items) {
indices = append(indices, i)
ports_list = append(ports_list, item["port"].(int))
}
Expect(indices).To(Equal([]int{0, 1}))
Expect(ports_list).To(Equal([]int{80, 443}))
})
})
Context("CollectionOp.Collect", func() {
It("should materialize iterator to slice using slices.Collect", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).
Filter(defkit.FieldEquals("expose", true))
items := []any{
map[string]any{"port": 80, "expose": true},
map[string]any{"port": 443, "expose": false},
}
results := col.Collect(items)
Expect(results).To(HaveLen(1))
Expect(results[0]["port"]).To(Equal(80))
})
})
Context("CollectionOp.Count", func() {
It("should count items after applying operations", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).
Filter(defkit.FieldEquals("expose", true))
items := []any{
map[string]any{"port": 80, "expose": true},
map[string]any{"port": 443, "expose": false},
map[string]any{"port": 8080, "expose": true},
}
Expect(col.Count(items)).To(Equal(2))
})
})
Context("CollectionOp.First", func() {
It("should return first item after applying operations", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).
Filter(defkit.FieldEquals("expose", true))
items := []any{
map[string]any{"port": 80, "expose": true},
map[string]any{"port": 443, "expose": false},
}
first := col.First(items)
Expect(first).NotTo(BeNil())
Expect(first["port"]).To(Equal(80))
})
It("should return nil for empty result", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).
Filter(defkit.FieldEquals("expose", true))
items := []any{
map[string]any{"port": 443, "expose": false},
}
first := col.First(items)
Expect(first).To(BeNil())
})
})
Context("FieldMap iterators", func() {
It("should iterate over all key-value pairs using maps.All", func() {
fm := defkit.FieldMap{
"containerPort": defkit.FieldRef("port"),
"name": defkit.FieldRef("name"),
}
var keys []string
for key := range fm.Keys() {
keys = append(keys, key)
}
Expect(keys).To(HaveLen(2))
Expect(keys).To(ContainElements("containerPort", "name"))
})
It("should iterate over all values using maps.Values", func() {
portRef := defkit.FieldRef("port")
nameRef := defkit.FieldRef("name")
fm := defkit.FieldMap{
"containerPort": portRef,
"name": nameRef,
}
var count int
for range fm.Values() {
count++
}
Expect(count).To(Equal(2))
})
It("should iterate over all key-value pairs using All", func() {
portRef := defkit.FieldRef("port")
nameRef := defkit.FieldRef("name")
fm := defkit.FieldMap{
"containerPort": portRef,
"name": nameRef,
}
pairs := make(map[string]defkit.FieldValue)
for key, val := range fm.All() {
pairs[key] = val
}
Expect(pairs).To(HaveLen(2))
Expect(pairs).To(HaveKey("containerPort"))
Expect(pairs).To(HaveKey("name"))
})
})
Context("MultiSource.All", func() {
It("should iterate over all items from multiple sources", func() {
volumeMounts := defkit.Object("volumeMounts")
ms := defkit.FromFields(volumeMounts, "pvc", "configMap").
Pick("name", "mountPath")
sourceData := map[string]any{
"pvc": []map[string]any{
{"name": "data", "mountPath": "/data", "claimName": "data-pvc"},
},
"configMap": []map[string]any{
{"name": "config", "mountPath": "/etc/config", "cmName": "app-config"},
},
}
var results []map[string]any
for item := range ms.All(sourceData) {
results = append(results, item)
}
Expect(results).To(HaveLen(2))
// Verify Pick only included specified fields
for _, r := range results {
Expect(r).To(HaveKey("name"))
Expect(r).To(HaveKey("mountPath"))
Expect(r).NotTo(HaveKey("claimName"))
Expect(r).NotTo(HaveKey("cmName"))
}
})
})
Context("MultiSource.Collect", func() {
It("should materialize iterator to slice", func() {
volumeMounts := defkit.Object("volumeMounts")
ms := defkit.FromFields(volumeMounts, "pvc", "configMap")
sourceData := map[string]any{
"pvc": []map[string]any{
{"name": "data"},
},
"configMap": []map[string]any{
{"name": "config"},
},
}
results := ms.Collect(sourceData)
Expect(results).To(HaveLen(2))
})
})
Context("MultiSource.Count", func() {
It("should count items from all sources", func() {
volumeMounts := defkit.Object("volumeMounts")
ms := defkit.FromFields(volumeMounts, "pvc", "configMap", "secret")
sourceData := map[string]any{
"pvc": []map[string]any{
{"name": "data1"},
{"name": "data2"},
},
"configMap": []map[string]any{
{"name": "config"},
},
// secret is empty
}
Expect(ms.Count(sourceData)).To(Equal(3))
})
})
Context("MultiSource.AllPairs", func() {
It("should iterate with index over all sources", func() {
volumeMounts := defkit.Object("volumeMounts")
ms := defkit.FromFields(volumeMounts, "pvc", "configMap")
sourceData := map[string]any{
"pvc": []map[string]any{
{"name": "data1"},
},
"configMap": []map[string]any{
{"name": "config"},
},
}
var indices []int
var names []string
for i, item := range ms.AllPairs(sourceData) {
indices = append(indices, i)
names = append(names, item["name"].(string))
}
Expect(indices).To(Equal([]int{0, 1}))
Expect(names).To(HaveLen(2))
})
})
Context("MapVariant chained operation behavior", func() {
It("should apply chained variant mappings to matching items and pass others through", func() {
volumes := defkit.List("volumes")
col := defkit.Each(volumes).
MapVariant("type", "pvc", defkit.FieldMap{
"pvcClaim": defkit.FieldRef("claimName"),
}).
MapVariant("type", "configMap", defkit.FieldMap{
"cmRef": defkit.FieldRef("cmName"),
}).
MapVariant("type", "emptyDir", defkit.FieldMap{
"medium": defkit.FieldRef("medium"),
})
items := []any{
map[string]any{"name": "data", "type": "pvc", "claimName": "data-pvc"},
map[string]any{"name": "config", "type": "configMap", "cmName": "app-config"},
map[string]any{"name": "cache", "type": "emptyDir", "medium": "Memory"},
}
results := col.Collect(items)
Expect(results).To(HaveLen(3))
// PVC item should have variant field merged, original fields preserved
Expect(results[0]["name"]).To(Equal("data"))
Expect(results[0]["pvcClaim"]).To(Equal("data-pvc"))
Expect(results[0]).NotTo(HaveKey("cmRef"))
// ConfigMap item should have variant field merged
Expect(results[1]["name"]).To(Equal("config"))
Expect(results[1]["cmRef"]).To(Equal("app-config"))
Expect(results[1]).NotTo(HaveKey("pvcClaim"))
// EmptyDir item should have variant field merged
Expect(results[2]["name"]).To(Equal("cache"))
Expect(results[2]["medium"]).To(Equal("Memory"))
})
It("should preserve items with no matching variant", func() {
volumes := defkit.List("volumes")
col := defkit.Each(volumes).
MapVariant("type", "pvc", defkit.FieldMap{
"pvcClaim": defkit.FieldRef("claimName"),
})
items := []any{
map[string]any{"name": "data", "type": "pvc", "claimName": "data-pvc"},
map[string]any{"name": "config", "type": "configMap", "cmName": "app-config"},
}
results := col.Collect(items)
Expect(results).To(HaveLen(2))
Expect(results[0]["pvcClaim"]).To(Equal("data-pvc"))
// Non-matching item passes through unchanged
Expect(results[1]["name"]).To(Equal("config"))
Expect(results[1]["type"]).To(Equal("configMap"))
})
})
Context("Flatten operation behavior", func() {
It("should flatten nested arrays", func() {
volumes := defkit.List("volumes")
col := defkit.Each(volumes).Flatten()
items := []any{
[]any{map[string]any{"name": "vol1"}},
[]any{map[string]any{"name": "vol2"}, map[string]any{"name": "vol3"}},
map[string]any{"name": "vol4"}, // non-array item should pass through
}
results := col.Collect(items)
Expect(results).To(HaveLen(4))
Expect(results[0]["name"]).To(Equal("vol1"))
Expect(results[1]["name"]).To(Equal("vol2"))
Expect(results[2]["name"]).To(Equal("vol3"))
Expect(results[3]["name"]).To(Equal("vol4"))
})
})
Context("DefaultField operation behavior", func() {
It("should set default value when field is missing", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).DefaultField("name", defkit.LitField("default-port"))
items := []any{
map[string]any{"port": 80}, // no name
map[string]any{"port": 443, "name": "https"}, // has name
map[string]any{"port": 8080, "name": ""}, // empty name
}
results := col.Collect(items)
Expect(results).To(HaveLen(3))
Expect(results[0]["name"]).To(Equal("default-port"))
Expect(results[1]["name"]).To(Equal("https"))
Expect(results[2]["name"]).To(Equal("default-port")) // empty string triggers default
})
})
Context("OrFieldRef behavior", func() {
It("should use fallback when primary field is nil", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).Map(defkit.FieldMap{
"displayName": defkit.FieldRef("name").Or(defkit.Format("port-%v", defkit.FieldRef("port"))),
})
items := []any{
map[string]any{"port": 80, "name": "http"},
map[string]any{"port": 443}, // name is nil
map[string]any{"port": 8080, "name": ""}, // name is empty
}
results := col.Collect(items)
Expect(results).To(HaveLen(3))
Expect(results[0]["displayName"]).To(Equal("http"))
Expect(results[1]["displayName"]).To(Equal("port-443"))
Expect(results[2]["displayName"]).To(Equal("port-8080"))
})
})
Context("OrConditional (ConditionalOrFieldRef)", func() {
It("should create ConditionalOrFieldRef that resolves primary field", func() {
ref := defkit.FieldRef("name").OrConditional(defkit.Format("port-%v", defkit.FieldRef("port")))
col := defkit.Each(defkit.List("items")).Map(defkit.FieldMap{"result": ref})
results := col.Collect([]any{map[string]any{"name": "web", "port": 80}})
Expect(results).To(HaveLen(1))
Expect(results[0]["result"]).To(Equal("web"))
})
It("should resolve primary vs fallback across multiple items with full structure", func() {
ports := defkit.List("ports")
col := defkit.Each(ports).Map(defkit.FieldMap{
"displayName": defkit.FieldRef("name").OrConditional(defkit.Format("port-%v", defkit.FieldRef("port"))),
"portNumber": defkit.FieldRef("port"),
"protocol": defkit.FieldRef("proto").OrConditional(defkit.LitField("TCP")),
})
items := []any{
map[string]any{"port": 80, "name": "http", "proto": "HTTP"}, // all primary fields present
map[string]any{"port": 443}, // name and proto nil → fallback
map[string]any{"port": 8080, "name": "", "proto": ""}, // name and proto empty → fallback
map[string]any{"port": 9090, "name": "grpc"}, // name present, proto nil → mixed
}
results := col.Collect(items)
Expect(results).To(HaveLen(4))
Expect(results[0]["displayName"]).To(Equal("http"))
Expect(results[0]["portNumber"]).To(Equal(80))
Expect(results[0]["protocol"]).To(Equal("HTTP"))
Expect(results[1]["displayName"]).To(Equal("port-443"))
Expect(results[1]["portNumber"]).To(Equal(443))
Expect(results[1]["protocol"]).To(Equal("TCP"))
Expect(results[2]["displayName"]).To(Equal("port-8080"))
Expect(results[2]["portNumber"]).To(Equal(8080))
Expect(results[2]["protocol"]).To(Equal("TCP"))
Expect(results[3]["displayName"]).To(Equal("grpc"))
Expect(results[3]["portNumber"]).To(Equal(9090))
Expect(results[3]["protocol"]).To(Equal("TCP"))
})
})
Context("FormatField RequiredImports", func() {
It("should require strconv for numeric formatting", func() {
f := defkit.Format("port-%v", defkit.FieldRef("port"))
imports := f.RequiredImports()
Expect(imports).To(ContainElement("strconv"))
})
It("should require strconv for %d formatting", func() {
f := defkit.Format("count-%d", defkit.FieldRef("count"))
imports := f.RequiredImports()
Expect(imports).To(ContainElement("strconv"))
})
It("should return empty when format doesn't need strconv", func() {
f := defkit.Format("name is %s", defkit.LitField("test"))
imports := f.RequiredImports()
Expect(imports).To(BeNil())
})
})
})
})