Add validate.UpdateValue, deprecate the By* forms

This is the last place that calls semantic DeepEqual directly.
This commit is contained in:
Tim Hockin authored and Yongrui Lin committed 2026-09-03 18:00:54 +00:00
1 parent b8d76348fa
commit bb954fb651
9 files changed
+231 -74

No files matched your search

+2 -1
View File
@@ -985,7 +985,8 @@ func Validate_NodeSpec(
if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkAlpha().MarkShortCircuit(); len(e) != 0 {
earlyReturn = true
}
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoUnset, validate.NoModify).MarkAlpha().MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a string, b string) bool { return a == b }, validate.NoUnset, validate.NoModify).MarkAlpha().MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -42,8 +42,8 @@ const (
NoRemoveItem
)
// UpdateValueByCompare verifies update constraints for comparable value types.
func UpdateValueByCompare[T comparable](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
// UpdateValue verifies update constraints for value types.
func UpdateValue[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, equiv MatchFunc[T], constraints ...UpdateConstraint) field.ErrorList {
// nil oldValue means no prior value to compare against (eg: a new item at +k8s:eachVal scope) -> no transition to check.
if op.Type != operation.Update || oldValue == nil {
return nil
@@ -51,22 +51,24 @@ func UpdateValueByCompare[T comparable](_ context.Context, op operation.Operatio
var errs field.ErrorList
var zero T
valueIsZero := equiv(*value, zero)
oldValueIsZero := equiv(*oldValue, zero)
for _, constraint := range constraints {
switch constraint {
case NoSet:
if *oldValue == zero && *value != zero {
if oldValueIsZero && !valueIsZero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update"))
}
case NoUnset:
if *oldValue != zero && *value == zero {
if !oldValueIsZero && valueIsZero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update"))
}
case NoModify:
// Rely on validation ratcheting to detect that the value has changed.
// This check only verifies that the field was set in both the old and
// new objects, confirming it was a modification, not a set/unset.
if *oldValue != zero && *value != zero {
if !oldValueIsZero && !valueIsZero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update"))
}
}
@@ -75,6 +77,13 @@ func UpdateValueByCompare[T comparable](_ context.Context, op operation.Operatio
return errs
}
// UpdateValueByCompare verifies update constraints for comparable value types.
//
// Deprecated: Use UpdateValue instead.
func UpdateValueByCompare[T comparable](ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
return UpdateValue(ctx, op, fldPath, value, oldValue, func(a, b T) bool { return a == b }, constraints...)
}
// UpdatePointer verifies update constraints for pointer types.
func UpdatePointer[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
if op.Type != operation.Update {
@@ -107,38 +116,10 @@ func UpdatePointer[T any](_ context.Context, op operation.Operation, fldPath *fi
}
// UpdateValueByReflect verifies update constraints for non-comparable value types using reflection.
func UpdateValueByReflect[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
// nil oldValue means no prior value to compare against (eg: a new item at +k8s:eachVal scope) -> no transition to check.
if op.Type != operation.Update || oldValue == nil {
return nil
}
var errs field.ErrorList
var zero T
valueIsZero := equality.Semantic.DeepEqual(*value, zero)
oldValueIsZero := equality.Semantic.DeepEqual(*oldValue, zero)
for _, constraint := range constraints {
switch constraint {
case NoSet:
if oldValueIsZero && !valueIsZero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update"))
}
case NoUnset:
if !oldValueIsZero && valueIsZero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update"))
}
case NoModify:
// Rely on validation ratcheting to detect that the value has changed.
// This check only verifies that the field was set in both the old and
// new objects, confirming it was a modification, not a set/unset.
if !oldValueIsZero && !valueIsZero {
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update"))
}
}
}
return errs
//
// Deprecated: Use UpdateValue instead.
func UpdateValueByReflect[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
return UpdateValue(ctx, op, fldPath, value, oldValue, func(a, b T) bool { return equality.Semantic.DeepEqual(a, b) }, constraints...)
}
// UpdateStruct verifies update constraints for non-pointer struct types.
@@ -138,10 +138,153 @@ func TestUpdateValue(t *testing.T) {
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
op := operation.Operation{Type: tt.op}
errs := UpdateValue(context.TODO(), op, field.NewPath("test"), &tt.value, &tt.oldValue, func(a, b string) bool { return a == b }, tt.constraints...)
if len(errs) != tt.wantErrs {
t.Errorf("UpdateValue() returned %d errors, want %d: %v", len(errs), tt.wantErrs, errs)
}
for i, msg := range tt.wantMsgs {
if i >= len(errs) {
t.Errorf("Expected error message %q not found", msg)
continue
}
if errs[i].Detail != msg {
t.Errorf("UpdateValue() error message = %q, want %q", errs[i].Detail, msg)
}
}
})
}
}
func TestUpdateValueByCompare(t *testing.T) {
tests := []struct {
name string
op operation.Type
value string
oldValue string
constraints []UpdateConstraint
wantErrs int
wantMsgs []string
}{
{
name: "NoSet - unset to set transition (forbidden)",
op: operation.Update,
value: "value",
oldValue: "",
constraints: []UpdateConstraint{NoSet},
wantErrs: 1,
wantMsgs: []string{"field cannot be set once created"},
},
{
name: "NoUnset - set to unset transition (forbidden)",
op: operation.Update,
value: "",
oldValue: "value",
constraints: []UpdateConstraint{NoUnset},
wantErrs: 1,
wantMsgs: []string{"field cannot be cleared once set"},
},
{
name: "NoModify - set to different value (forbidden)",
op: operation.Update,
value: "value2",
oldValue: "value1",
constraints: []UpdateConstraint{NoModify},
wantErrs: 1,
wantMsgs: []string{"field cannot be modified once set"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
op := operation.Operation{Type: tt.op}
errs := UpdateValueByCompare(context.TODO(), op, field.NewPath("test"), &tt.value, &tt.oldValue, tt.constraints...)
if len(errs) != tt.wantErrs {
t.Errorf("UpdateValueByCompare() returned %d errors, want %d: %v", len(errs), tt.wantErrs, errs)
}
for i, msg := range tt.wantMsgs {
if i >= len(errs) {
t.Errorf("Expected error message %q not found", msg)
continue
}
if errs[i].Detail != msg {
t.Errorf("UpdateValueByCompare() error message = %q, want %q", errs[i].Detail, msg)
}
}
})
}
}
func TestUpdateValue_CustomMatchFunc(t *testing.T) {
type NonComparableStruct struct {
Name string
Tags []string
}
customEquiv := func(a, b NonComparableStruct) bool {
return a.Name == b.Name && (len(a.Tags) == 0 && len(b.Tags) == 0 || (len(a.Tags) == len(b.Tags) && a.Tags[0] == b.Tags[0]))
}
tests := []struct {
name string
op operation.Type
value NonComparableStruct
oldValue NonComparableStruct
constraints []UpdateConstraint
wantErrs int
wantMsgs []string
}{
{
name: "NoSet - unset to set (forbidden)",
op: operation.Update,
value: NonComparableStruct{Name: "foo", Tags: []string{"a"}},
oldValue: NonComparableStruct{},
constraints: []UpdateConstraint{NoSet},
wantErrs: 1,
wantMsgs: []string{"field cannot be set once created"},
},
{
name: "NoUnset - set to unset (forbidden)",
op: operation.Update,
value: NonComparableStruct{},
oldValue: NonComparableStruct{Name: "foo", Tags: []string{"a"}},
constraints: []UpdateConstraint{NoUnset},
wantErrs: 1,
wantMsgs: []string{"field cannot be cleared once set"},
},
{
name: "NoModify - zero to non-zero transition (allowed)",
op: operation.Update,
value: NonComparableStruct{Name: "foo", Tags: []string{"a"}},
oldValue: NonComparableStruct{},
constraints: []UpdateConstraint{NoModify},
wantErrs: 0,
},
{
name: "NoModify - non-zero to zero transition (allowed)",
op: operation.Update,
value: NonComparableStruct{},
oldValue: NonComparableStruct{Name: "foo", Tags: []string{"a"}},
constraints: []UpdateConstraint{NoModify},
wantErrs: 0,
},
{
name: "NoModify - non-zero to non-zero transition (forbidden)",
op: operation.Update,
value: NonComparableStruct{Name: "foo", Tags: []string{"b"}},
oldValue: NonComparableStruct{Name: "foo", Tags: []string{"a"}},
constraints: []UpdateConstraint{NoModify},
wantErrs: 1,
wantMsgs: []string{"field cannot be modified once set"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
op := operation.Operation{Type: tt.op}
errs := UpdateValue(context.TODO(), op, field.NewPath("test"), &tt.value, &tt.oldValue, customEquiv, tt.constraints...)
if len(errs) != tt.wantErrs {
t.Errorf("UpdateValue() returned %d errors, want %d: %v", len(errs), tt.wantErrs, errs)
}
@@ -219,7 +219,8 @@ func Validate_SpecialValidationStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkAlpha().MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a string, b string) bool { return a == b }, validate.NoModify).MarkAlpha().MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -248,7 +249,8 @@ func Validate_SpecialValidationStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkBeta().MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a string, b string) bool { return a == b }, validate.NoModify).MarkBeta().MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -258,7 +258,8 @@ func Validate_Struct(
if e := validate.OptionalValue(ctx, op, fldPath, obj, oldObj).MarkShortCircuit(); len(e) != 0 {
earlyReturn = true
}
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoUnset).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a int, b int) bool { return a == b }, validate.NoUnset).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -136,7 +136,8 @@ func Validate_Struct(
earlyReturn := false
if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", true,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
return validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify)
return validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a string, b string) bool { return a == b }, validate.NoModify)
}).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
@@ -168,7 +169,8 @@ func Validate_Struct(
earlyReturn := false
if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "FeatureX", false,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
return validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify)
return validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a string, b string) bool { return a == b }, validate.NoModify)
}).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
@@ -1029,7 +1029,8 @@ func Validate_ParentWithUpdate(
if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "value",
func(o *TargetWithUpdate) *string { return &o.Value }, validate.DirectEqual,
func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
return validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify)
return validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a string, b string) bool { return a == b }, validate.NoModify)
}).MarkShortCircuit(); len(e) != 0 {
earlyReturn = true
}
@@ -1308,7 +1309,8 @@ func Validate_TargetWithUpdate(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a string, b string) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -77,7 +77,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoSet).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a string, b string) bool { return a == b }, validate.NoSet).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -106,7 +107,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoUnset).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a string, b string) bool { return a == b }, validate.NoUnset).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -135,7 +137,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a string, b string) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -164,7 +167,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoSet, validate.NoUnset, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a string, b string) bool { return a == b }, validate.NoSet, validate.NoUnset, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -193,7 +197,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoUnset, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a string, b string) bool { return a == b }, validate.NoUnset, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -222,7 +227,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a int, b int) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -251,7 +257,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a int32, b int32) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -280,7 +287,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a int64, b int64) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -309,7 +317,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a uint, b uint) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -338,7 +347,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a bool, b bool) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -367,7 +377,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a float32, b float32) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -396,7 +407,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a float64, b float64) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -425,7 +437,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a byte, b byte) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -715,7 +728,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a CustomString, b CustomString) bool { return a == b }, validate.NoModify).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -744,7 +758,8 @@ func Validate_UpdateTestStruct(
}
// call field-attached validations
earlyReturn := false
if e := validate.UpdateValueByCompare(ctx, op, fldPath, obj, oldObj, validate.NoSet).MarkShortCircuit(); len(e) != 0 {
if e := validate.UpdateValue(ctx, op, fldPath, obj, oldObj,
func(a CustomInt, b CustomInt) bool { return a == b }, validate.NoSet).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
@@ -199,13 +199,12 @@ func (utc updateTagCollector) Docs() TagDoc {
}
var (
updateValueValidator = types.Name{Package: libValidationPkg, Name: "UpdateValueByCompare"}
updatePointerValidator = types.Name{Package: libValidationPkg, Name: "UpdatePointer"}
updateValueByReflectValidator = types.Name{Package: libValidationPkg, Name: "UpdateValueByReflect"}
updateStructValidator = types.Name{Package: libValidationPkg, Name: "UpdateStruct"}
valSliceUpdateValidator = types.Name{Package: libValidationPkg, Name: "ValSliceUpdate"}
ptrSliceUpdateValidator = types.Name{Package: libValidationPkg, Name: "PtrSliceUpdate"}
updateMapValidator = types.Name{Package: libValidationPkg, Name: "UpdateMap"}
updateValueValidator = types.Name{Package: libValidationPkg, Name: "UpdateValue"}
updatePointerValidator = types.Name{Package: libValidationPkg, Name: "UpdatePointer"}
updateStructValidator = types.Name{Package: libValidationPkg, Name: "UpdateStruct"}
valSliceUpdateValidator = types.Name{Package: libValidationPkg, Name: "ValSliceUpdate"}
ptrSliceUpdateValidator = types.Name{Package: libValidationPkg, Name: "PtrSliceUpdate"}
updateMapValidator = types.Name{Package: libValidationPkg, Name: "UpdateMap"}
// Constraint constants that will be used as arguments
noSetConstraint = types.Name{Package: libValidationPkg, Name: "NoSet"}
@@ -314,9 +313,8 @@ func generateMapValidation(constraints []validate.UpdateConstraint) Validations
return Validations{Functions: []FunctionGen{fn}}
}
// emitScalarUpdate emits a call to
// UpdateValueByCompare/UpdatePointer/UpdateStruct/UpdateValueByReflect based
// on the Go kind of context.Type.
// emitScalarUpdate emits a call to UpdateValue/UpdatePointer/UpdateStruct
// based on the Go kind of context.Type.
// Used for both field scope (scalar/pointer/struct) and list/map element
// scope via +k8s:eachVal.
func emitScalarUpdate(context Context, constraints []validate.UpdateConstraint) Validations {
@@ -327,21 +325,33 @@ func emitScalarUpdate(context Context, constraints []validate.UpdateConstraint)
isComparable := util.IsDirectComparable(t)
var validatorFunc types.Name
var args []any
if isPointer {
validatorFunc = updatePointerValidator
args = constraintIdentifierArgs(constraints)
} else if isStruct {
validatorFunc = updateStructValidator
} else if isComparable {
validatorFunc = updateValueValidator
args = constraintIdentifierArgs(constraints)
} else {
validatorFunc = updateValueByReflectValidator
validatorFunc = updateValueValidator
var matchArg any
if isComparable {
matchArg = FunctionLiteral{
Parameters: []ParamResult{{"a", context.Type}, {"b", context.Type}},
Results: []ParamResult{{"", types.Bool}},
Body: "return a == b",
}
} else {
matchArg = Identifier(validateSemanticDeepEqual)
}
args = append([]any{matchArg}, constraintIdentifierArgs(constraints)...)
}
// Use ShortCircuit flag so these run in the same group as +k8s:optional.
// Scalar/pointer/struct fields only accept NoSet/NoUnset/NoModify
// (validateConstraintsForType rejects the rest), all of which emit
// field.Invalid at the field path.
fn := Function(updateTagName, ShortCircuit, validatorFunc, constraintIdentifierArgs(constraints)...).
fn := Function(updateTagName, ShortCircuit, validatorFunc, args...).
WithEmits(Emission{field.ErrorTypeInvalid, "update", ""})
return Validations{Functions: []FunctionGen{fn}}
}