mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-18 04:54:54 +00:00
chore: add +k8s:immutable tag implementation and test
This commit is contained in:
@@ -19,46 +19,22 @@ package validate
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// ImmutableByCompare verifies that the specified value has not changed in the
|
||||
// course of an update operation. It does nothing if the old value is not
|
||||
// provided. If the caller needs to compare types that are not trivially
|
||||
// comparable, they should use ImmutableByReflect instead.
|
||||
// Immutable verifies that the specified value has not changed in the course of
|
||||
// an update operation. It does nothing if the old value is not provided.
|
||||
//
|
||||
// Caution: structs with pointer fields satisfy comparable, but this function
|
||||
// will only compare pointer values. It does not compare the pointed-to
|
||||
// values.
|
||||
func ImmutableByCompare[T comparable](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T) field.ErrorList {
|
||||
// This function unconditionally returns a validation error as it
|
||||
// relies on the default ratcheting mechanism to only be called when a
|
||||
// change to the field has already been detected. This avoids a redundant
|
||||
// equivalence check across ratcheting and this function.
|
||||
func Immutable[T any](_ context.Context, op operation.Operation, fldPath *field.Path, _, _ T) field.ErrorList {
|
||||
if op.Type != operation.Update {
|
||||
return nil
|
||||
}
|
||||
if value == nil && oldValue == nil {
|
||||
return nil
|
||||
return field.ErrorList{
|
||||
field.Invalid(fldPath, nil, "field is immutable").WithOrigin("immutable"),
|
||||
}
|
||||
if value == nil || oldValue == nil || *value != *oldValue {
|
||||
return field.ErrorList{
|
||||
field.Forbidden(fldPath, "field is immutable"),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ImmutableByReflect verifies that the specified value has not changed in
|
||||
// the course of an update operation. It does nothing if the old value is not
|
||||
// provided. Unlike ImmutableByCompare, this function can be used with types that are
|
||||
// not directly comparable, at the cost of performance.
|
||||
func ImmutableByReflect[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue T) field.ErrorList {
|
||||
if op.Type != operation.Update {
|
||||
return nil
|
||||
}
|
||||
if !equality.Semantic.DeepEqual(value, oldValue) {
|
||||
return field.ErrorList{
|
||||
field.Forbidden(fldPath, "field is immutable"),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,225 +25,51 @@ import (
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
type StructComparable struct {
|
||||
S string
|
||||
I int
|
||||
B bool
|
||||
}
|
||||
func TestImmutable(t *testing.T) {
|
||||
// The Immutable function relies on validation ratcheting to avoid being
|
||||
// called when old and new values are equivalent. This unit test only needs
|
||||
// to confirm two behaviors:
|
||||
// 1. The function does nothing for non-update operations (e.g., create).
|
||||
// 2. The function *always* returns an error for update operations, since
|
||||
// ratcheting should have prevented the call if the values were unchanged.
|
||||
|
||||
func TestImmutableByCompare(t *testing.T) {
|
||||
structA := StructComparable{"abc", 123, true}
|
||||
structA2 := structA
|
||||
structB := StructComparable{"xyz", 456, false}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
fn func(operation.Operation, *field.Path) field.ErrorList
|
||||
fail bool
|
||||
}{{
|
||||
name: "nil both values",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByCompare[int](context.Background(), op, fld, nil, nil)
|
||||
},
|
||||
}, {
|
||||
name: "nil value",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByCompare(context.Background(), op, fld, nil, ptr.To(123))
|
||||
},
|
||||
fail: true,
|
||||
}, {
|
||||
name: "nil oldValue",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByCompare(context.Background(), op, fld, ptr.To(123), nil)
|
||||
},
|
||||
fail: true,
|
||||
}, {
|
||||
name: "int",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByCompare(context.Background(), op, fld, ptr.To(123), ptr.To(123))
|
||||
},
|
||||
}, {
|
||||
name: "int fail",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByCompare(context.Background(), op, fld, ptr.To(123), ptr.To(456))
|
||||
},
|
||||
fail: true,
|
||||
}, {
|
||||
name: "string",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByCompare(context.Background(), op, fld, ptr.To("abc"), ptr.To("abc"))
|
||||
},
|
||||
}, {
|
||||
name: "string fail",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByCompare(context.Background(), op, fld, ptr.To("abc"), ptr.To("xyz"))
|
||||
},
|
||||
fail: true,
|
||||
}, {
|
||||
name: "bool",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByCompare(context.Background(), op, fld, ptr.To(true), ptr.To(true))
|
||||
},
|
||||
}, {
|
||||
name: "bool fail",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByCompare(context.Background(), op, fld, ptr.To(true), ptr.To(false))
|
||||
},
|
||||
fail: true,
|
||||
}, {
|
||||
name: "same struct",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByCompare(context.Background(), op, fld, ptr.To(structA), ptr.To(structA))
|
||||
},
|
||||
}, {
|
||||
name: "equal struct",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByCompare(context.Background(), op, fld, ptr.To(structA), ptr.To(structA2))
|
||||
},
|
||||
}, {
|
||||
name: "struct fail",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByCompare(context.Background(), op, fld, ptr.To(structA), ptr.To(structB))
|
||||
},
|
||||
fail: true,
|
||||
}} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
errs := tc.fn(operation.Operation{Type: operation.Create}, field.NewPath(""))
|
||||
if len(errs) != 0 { // Create should always succeed
|
||||
t.Errorf("case %q (create): expected success: %v", tc.name, errs)
|
||||
}
|
||||
errs = tc.fn(operation.Operation{Type: operation.Update}, field.NewPath(""))
|
||||
if tc.fail && len(errs) == 0 {
|
||||
t.Errorf("case %q (update): expected failure", tc.name)
|
||||
} else if !tc.fail && len(errs) != 0 {
|
||||
t.Errorf("case %q (update): expected success: %v", tc.name, errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type StructNonComparable struct {
|
||||
S string
|
||||
SP *string
|
||||
I int
|
||||
IP *int
|
||||
B bool
|
||||
BP *bool
|
||||
SS []string
|
||||
MSS map[string]string
|
||||
}
|
||||
|
||||
func TestImmutableByReflect(t *testing.T) {
|
||||
structA := StructNonComparable{
|
||||
S: "abc",
|
||||
SP: ptr.To("abc"),
|
||||
I: 123,
|
||||
IP: ptr.To(123),
|
||||
B: true,
|
||||
BP: ptr.To(true),
|
||||
SS: []string{"a", "b", "c"},
|
||||
MSS: map[string]string{"a": "b", "c": "d"},
|
||||
}
|
||||
|
||||
structA2 := structA
|
||||
structA2.SP = ptr.To("abc")
|
||||
structA2.IP = ptr.To(123)
|
||||
structA2.BP = ptr.To(true)
|
||||
structA2.SS = []string{"a", "b", "c"}
|
||||
structA2.MSS = map[string]string{"a": "b", "c": "d"}
|
||||
|
||||
structB := StructNonComparable{
|
||||
S: "xyz",
|
||||
SP: ptr.To("xyz"),
|
||||
I: 456,
|
||||
IP: ptr.To(456),
|
||||
B: false,
|
||||
BP: ptr.To(false),
|
||||
SS: []string{"x", "y", "z"},
|
||||
MSS: map[string]string{"x": "X", "y": "Y"},
|
||||
type simpleStruct struct {
|
||||
S string
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
fn func(operation.Operation, *field.Path) field.ErrorList
|
||||
fail bool
|
||||
fn func(op operation.Operation, fldPath *field.Path) field.ErrorList
|
||||
}{{
|
||||
name: "nil both values",
|
||||
name: "with primitive type",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByReflect[*int](context.Background(), op, fld, nil, nil)
|
||||
return Immutable(context.Background(), op, fld, ptr.To(123), ptr.To(456))
|
||||
},
|
||||
}, {
|
||||
name: "nil value",
|
||||
name: "with struct type",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByReflect(context.Background(), op, fld, nil, ptr.To(123))
|
||||
},
|
||||
fail: true,
|
||||
}, {
|
||||
name: "nil oldValue",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByReflect(context.Background(), op, fld, ptr.To(123), nil)
|
||||
},
|
||||
fail: true,
|
||||
}, {
|
||||
name: "int",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByReflect(context.Background(), op, fld, ptr.To(123), ptr.To(123))
|
||||
return Immutable(context.Background(), op, fld, &simpleStruct{S: "a"}, &simpleStruct{S: "b"})
|
||||
},
|
||||
}, {
|
||||
name: "int fail",
|
||||
name: "with nil values",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByReflect(context.Background(), op, fld, ptr.To(123), ptr.To(456))
|
||||
// Explicitly type the nil to satisfy the generic function signature.
|
||||
return Immutable[*int](context.Background(), op, fld, nil, nil)
|
||||
},
|
||||
fail: true,
|
||||
}, {
|
||||
name: "string",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByReflect(context.Background(), op, fld, ptr.To("abc"), ptr.To("abc"))
|
||||
},
|
||||
}, {
|
||||
name: "string fail",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByReflect(context.Background(), op, fld, ptr.To("abc"), ptr.To("xyz"))
|
||||
},
|
||||
fail: true,
|
||||
}, {
|
||||
name: "bool",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByReflect(context.Background(), op, fld, ptr.To(true), ptr.To(true))
|
||||
},
|
||||
}, {
|
||||
name: "bool fail",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByReflect(context.Background(), op, fld, ptr.To(true), ptr.To(false))
|
||||
},
|
||||
fail: true,
|
||||
}, {
|
||||
name: "same struct",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByReflect(context.Background(), op, fld, ptr.To(structA), ptr.To(structA))
|
||||
},
|
||||
}, {
|
||||
name: "equal struct",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByReflect(context.Background(), op, fld, ptr.To(structA), ptr.To(structA2))
|
||||
},
|
||||
}, {
|
||||
name: "struct fail",
|
||||
fn: func(op operation.Operation, fld *field.Path) field.ErrorList {
|
||||
return ImmutableByReflect(context.Background(), op, fld, ptr.To(structA), ptr.To(structB))
|
||||
},
|
||||
fail: true,
|
||||
}} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
errs := tc.fn(operation.Operation{Type: operation.Create}, field.NewPath(""))
|
||||
if len(errs) != 0 { // Create should always succeed
|
||||
t.Errorf("case %q (create): expected success: %v", tc.name, errs)
|
||||
// Create operations should never return an error.
|
||||
errs := tc.fn(operation.Operation{Type: operation.Create}, field.NewPath("field"))
|
||||
if len(errs) != 0 {
|
||||
t.Errorf("expected success for create operation, but got errors: %v", errs)
|
||||
}
|
||||
errs = tc.fn(operation.Operation{Type: operation.Update}, field.NewPath(""))
|
||||
if tc.fail && len(errs) == 0 {
|
||||
t.Errorf("case %q (update): expected failure", tc.name)
|
||||
} else if !tc.fail && len(errs) != 0 {
|
||||
t.Errorf("case %q (update): expected success: %v", tc.name, errs)
|
||||
|
||||
// Update operations should always return exactly one error.
|
||||
errs = tc.fn(operation.Operation{Type: operation.Update}, field.NewPath("field"))
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("expected a failure for update operation, but got success")
|
||||
} else if len(errs) > 1 {
|
||||
t.Errorf("expected exactly one error for update operation, but got %d: %v", len(errs), errs)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -64,16 +64,16 @@ func Test(t *testing.T) {
|
||||
st.Value(&structA).OldValue(&structA2).ExpectValid()
|
||||
st.Value(&structA2).OldValue(&structA).ExpectValid()
|
||||
|
||||
st.Value(&structA).OldValue(&structB).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{
|
||||
field.Forbidden(field.NewPath("stringField"), "immutable"),
|
||||
field.Forbidden(field.NewPath("stringPtrField"), "immutable"),
|
||||
field.Forbidden(field.NewPath("structField"), "immutable"),
|
||||
field.Forbidden(field.NewPath("structPtrField"), "immutable"),
|
||||
field.Forbidden(field.NewPath("noncomparableStructField"), "immutable"),
|
||||
field.Forbidden(field.NewPath("noncomparableStructPtrField"), "immutable"),
|
||||
field.Forbidden(field.NewPath("sliceField"), "immutable"),
|
||||
field.Forbidden(field.NewPath("mapField"), "immutable"),
|
||||
field.Forbidden(field.NewPath("immutableField"), "immutable"),
|
||||
field.Forbidden(field.NewPath("immutablePtrField"), "immutable"),
|
||||
st.Value(&structA).OldValue(&structB).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByOrigin(), field.ErrorList{
|
||||
field.Invalid(field.NewPath("stringField"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("stringPtrField"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("structField"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("structPtrField"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("noncomparableStructField"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("noncomparableStructPtrField"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("sliceField"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("mapField"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("immutableField"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("immutablePtrField"), nil, "").WithOrigin("immutable"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -52,7 +52,10 @@ func RegisterValidations(scheme *testscheme.Scheme) error {
|
||||
// Validate_ImmutableType validates an instance of ImmutableType according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_ImmutableType(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ImmutableType) (errs field.ErrorList) {
|
||||
errs = append(errs, validate.ImmutableByCompare(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
@@ -70,7 +73,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByCompare(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("stringField"), &obj.StringField, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.StringField }))...)
|
||||
|
||||
@@ -82,7 +88,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByCompare(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("stringPtrField"), obj.StringPtrField, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.StringPtrField }))...)
|
||||
|
||||
@@ -94,7 +103,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByReflect(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("structField"), &obj.StructField, safe.Field(oldObj, func(oldObj *Struct) *ComparableStruct { return &oldObj.StructField }))...)
|
||||
|
||||
@@ -106,7 +118,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByReflect(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("structPtrField"), obj.StructPtrField, safe.Field(oldObj, func(oldObj *Struct) *ComparableStruct { return oldObj.StructPtrField }))...)
|
||||
|
||||
@@ -118,7 +133,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByReflect(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("noncomparableStructField"), &obj.NonComparableStructField, safe.Field(oldObj, func(oldObj *Struct) *NonComparableStruct { return &oldObj.NonComparableStructField }))...)
|
||||
|
||||
@@ -130,7 +148,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByReflect(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("noncomparableStructPtrField"), obj.NonComparableStructPtrField, safe.Field(oldObj, func(oldObj *Struct) *NonComparableStruct { return oldObj.NonComparableStructPtrField }))...)
|
||||
|
||||
@@ -142,7 +163,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByReflect(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("sliceField"), obj.SliceField, safe.Field(oldObj, func(oldObj *Struct) []string { return oldObj.SliceField }))...)
|
||||
|
||||
@@ -154,7 +178,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByReflect(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("mapField"), obj.MapField, safe.Field(oldObj, func(oldObj *Struct) map[string]string { return oldObj.MapField }))...)
|
||||
|
||||
|
||||
@@ -42,13 +42,13 @@ func Test(t *testing.T) {
|
||||
}
|
||||
|
||||
st.Value(new).OldValue(old).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{
|
||||
field.Forbidden(field.NewPath("listField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("listField").Index(1).Child("stringField"), "immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(1).Child("stringField"), nil, "immutable").WithOrigin("immutable"),
|
||||
})
|
||||
|
||||
st.Value(new).OldValue(&Struct{ListField: []Item{}}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{
|
||||
field.Forbidden(field.NewPath("listField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("listField").Index(1).Child("stringField"), "immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(1).Child("stringField"), nil, "immutable").WithOrigin("immutable"),
|
||||
})
|
||||
|
||||
// Test that "c" can change independently
|
||||
|
||||
@@ -65,12 +65,18 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a Item, b Item) bool { return a.Key1 == b.Key1 })...)
|
||||
func() { // cohort {"key1": "a"}
|
||||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key1 == "a" }, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
if e := validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key1 == "a" }, validate.DirectEqual, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
}()
|
||||
func() { // cohort {"key1": "b"}
|
||||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key1 == "b" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList {
|
||||
return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *Item) *string { return &o.StringField }, validate.DirectEqualPtr, validate.ImmutableByCompare)
|
||||
})...)
|
||||
if e := validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key1 == "b" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList {
|
||||
return validate.Subfield(ctx, op, fldPath, obj, oldObj, "stringField", func(o *Item) *string { return &o.StringField }, validate.DirectEqualPtr, validate.Immutable)
|
||||
}); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
}()
|
||||
return
|
||||
}(fldPath.Child("listField"), obj.ListField, safe.Field(oldObj, func(oldObj *Struct) []Item { return oldObj.ListField }))...)
|
||||
|
||||
@@ -53,7 +53,7 @@ func Test(t *testing.T) {
|
||||
},
|
||||
}
|
||||
st.Value(newStruct).OldValue(oldStruct).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{
|
||||
field.Forbidden(field.NewPath("typedefItems").Index(0), "immutable"),
|
||||
field.Invalid(field.NewPath("typedefItems").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
})
|
||||
|
||||
// Test nested typedef (typedef of typedef).
|
||||
|
||||
@@ -83,7 +83,10 @@ func Validate_ItemList(ctx context.Context, op operation.Operation, fldPath *fie
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a Item, b Item) bool { return a.Key == b.Key })...)
|
||||
func() { // cohort {"key": "immutable"}
|
||||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key == "immutable" }, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
if e := validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key == "immutable" }, validate.DirectEqual, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
}()
|
||||
func() { // cohort {"key": "validated"}
|
||||
errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.Key == "validated" }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList {
|
||||
|
||||
@@ -106,24 +106,24 @@ func TestUpdateCorrelation(t *testing.T) {
|
||||
st.Value(&structA2).OldValue(&structA1).ExpectValid()
|
||||
|
||||
st.Value(&structA1).OldValue(&structB).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{
|
||||
field.Forbidden(field.NewPath("listField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("listField").Index(1), "immutable"),
|
||||
field.Forbidden(field.NewPath("listTypedefField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("listTypedefField").Index(1), "immutable"),
|
||||
field.Forbidden(field.NewPath("typedefField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("typedefField").Index(1), "immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(1), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listTypedefField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listTypedefField").Index(1), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("typedefField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("typedefField").Index(1), nil, "immutable").WithOrigin("immutable"),
|
||||
})
|
||||
|
||||
st.Value(&structB).OldValue(&structA1).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{
|
||||
field.Forbidden(field.NewPath("listField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("listField").Index(1), "immutable"),
|
||||
field.Forbidden(field.NewPath("listField").Index(2), "immutable"),
|
||||
field.Forbidden(field.NewPath("listTypedefField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("listTypedefField").Index(1), "immutable"),
|
||||
field.Forbidden(field.NewPath("listTypedefField").Index(2), "immutable"),
|
||||
field.Forbidden(field.NewPath("typedefField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("typedefField").Index(1), "immutable"),
|
||||
field.Forbidden(field.NewPath("typedefField").Index(2), "immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(1), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(2), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listTypedefField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listTypedefField").Index(1), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listTypedefField").Index(2), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("typedefField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("typedefField").Index(1), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("typedefField").Index(2), nil, "immutable").WithOrigin("immutable"),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -73,9 +73,12 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool {
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
}, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
}, validate.DirectEqual, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
@@ -91,9 +94,12 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherTypedefStruct, b OtherTypedefStruct) bool {
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherTypedefStruct, b OtherTypedefStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
}, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
}, validate.DirectEqual, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherTypedefStruct, b OtherTypedefStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
@@ -109,9 +115,12 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool {
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool {
|
||||
return a.Key1Field == b.Key1Field && a.Key2Field == b.Key2Field
|
||||
}, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
}, validate.DirectEqual, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_ListType(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
|
||||
@@ -107,24 +107,24 @@ func TestUpdateCorrelation(t *testing.T) {
|
||||
st.Value(&structA2).OldValue(&structA1).ExpectValid()
|
||||
|
||||
st.Value(&structA1).OldValue(&structB).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{
|
||||
field.Forbidden(field.NewPath("listField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("listField").Index(1), "immutable"),
|
||||
field.Forbidden(field.NewPath("listTypedefField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("listTypedefField").Index(1), "immutable"),
|
||||
field.Forbidden(field.NewPath("typedefField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("typedefField").Index(1), "immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(1), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listTypedefField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listTypedefField").Index(1), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("typedefField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("typedefField").Index(1), nil, "immutable").WithOrigin("immutable"),
|
||||
})
|
||||
|
||||
st.Value(&structB).OldValue(&structA1).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{
|
||||
field.Forbidden(field.NewPath("listField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("listField").Index(1), "immutable"),
|
||||
field.Forbidden(field.NewPath("listField").Index(2), "immutable"),
|
||||
field.Forbidden(field.NewPath("listTypedefField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("listTypedefField").Index(1), "immutable"),
|
||||
field.Forbidden(field.NewPath("listTypedefField").Index(2), "immutable"),
|
||||
field.Forbidden(field.NewPath("typedefField").Index(0), "immutable"),
|
||||
field.Forbidden(field.NewPath("typedefField").Index(1), "immutable"),
|
||||
field.Forbidden(field.NewPath("typedefField").Index(2), "immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(1), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listField").Index(2), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listTypedefField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listTypedefField").Index(1), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("listTypedefField").Index(2), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("typedefField").Index(0), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("typedefField").Index(1), nil, "immutable").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("typedefField").Index(2), nil, "immutable").WithOrigin("immutable"),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.KeyField == b.KeyField })...)
|
||||
return
|
||||
@@ -85,7 +88,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherTypedefStruct, b OtherTypedefStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherTypedefStruct, b OtherTypedefStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// lists with map semantics require unique keys
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, func(a OtherTypedefStruct, b OtherTypedefStruct) bool { return a.KeyField == b.KeyField })...)
|
||||
return
|
||||
@@ -99,7 +105,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, validate.ImmutableByCompare)...)
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, func(a OtherStruct, b OtherStruct) bool { return a.KeyField == b.KeyField }, validate.DirectEqual, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// call the type's validation function
|
||||
errs = append(errs, Validate_ListType(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
|
||||
@@ -70,7 +70,10 @@ func Validate_ImmutableStruct(ctx context.Context, op operation.Operation, fldPa
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, validate.ImmutableByCompare)...)
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("sliceComparableField"), obj.SliceComparableField, safe.Field(oldObj, func(oldObj *ImmutableStruct) []ComparableStruct { return oldObj.SliceComparableField }))...)
|
||||
|
||||
@@ -82,7 +85,10 @@ func Validate_ImmutableStruct(ctx context.Context, op operation.Operation, fldPa
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, validate.ImmutableByCompare)...)
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...)
|
||||
return
|
||||
@@ -96,7 +102,10 @@ func Validate_ImmutableStruct(ctx context.Context, op operation.Operation, fldPa
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, validate.ImmutableByReflect)...)
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("sliceNonComparableField"), obj.SliceNonComparableField, safe.Field(oldObj, func(oldObj *ImmutableStruct) []NonComparableStruct { return oldObj.SliceNonComparableField }))...)
|
||||
|
||||
@@ -108,7 +117,10 @@ func Validate_ImmutableStruct(ctx context.Context, op operation.Operation, fldPa
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, nil, validate.ImmutableByReflect)...)
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, nil, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual)...)
|
||||
return
|
||||
@@ -122,7 +134,10 @@ func Validate_ImmutableStruct(ctx context.Context, op operation.Operation, fldPa
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, validate.ImmutableByCompare)...)
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, nil, nil, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("slicePrimitiveField"), obj.SlicePrimitiveField, safe.Field(oldObj, func(oldObj *ImmutableStruct) []int { return oldObj.SlicePrimitiveField }))...)
|
||||
|
||||
@@ -134,7 +149,10 @@ func Validate_ImmutableStruct(ctx context.Context, op operation.Operation, fldPa
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, validate.ImmutableByCompare)...)
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.DirectEqual, nil, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.DirectEqual)...)
|
||||
return
|
||||
@@ -148,7 +166,10 @@ func Validate_ImmutableStruct(ctx context.Context, op operation.Operation, fldPa
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, nil, validate.ImmutableByReflect)...)
|
||||
if e := validate.EachSliceVal(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual, nil, validate.Immutable); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
// lists with set semantics require unique values
|
||||
errs = append(errs, validate.Unique(ctx, op, fldPath, obj, oldObj, validate.SemanticDeepEqual)...)
|
||||
return
|
||||
|
||||
@@ -53,9 +53,9 @@ func Test(t *testing.T) {
|
||||
st.Value(&structA1).OldValue(&structA2).ExpectValid()
|
||||
|
||||
st.Value(&structA1).OldValue(&structB).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{
|
||||
field.Forbidden(field.NewPath("sp"), ""),
|
||||
field.Forbidden(field.NewPath("ip"), ""),
|
||||
field.Forbidden(field.NewPath("bp"), ""),
|
||||
field.Forbidden(field.NewPath("fp"), ""),
|
||||
field.Invalid(field.NewPath("sp"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("ip"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("bp"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("fp"), nil, "").WithOrigin("immutable"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -59,7 +59,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByCompare(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("sp"), obj.SP, safe.Field(oldObj, func(oldObj *Struct) *string { return oldObj.SP }))...)
|
||||
|
||||
@@ -71,7 +74,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByCompare(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("ip"), obj.IP, safe.Field(oldObj, func(oldObj *Struct) *int { return oldObj.IP }))...)
|
||||
|
||||
@@ -83,7 +89,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByCompare(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("bp"), obj.BP, safe.Field(oldObj, func(oldObj *Struct) *bool { return oldObj.BP }))...)
|
||||
|
||||
@@ -95,7 +104,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByCompare(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("fp"), obj.FP, safe.Field(oldObj, func(oldObj *Struct) *float64 { return oldObj.FP }))...)
|
||||
|
||||
|
||||
@@ -43,9 +43,9 @@ func Test(t *testing.T) {
|
||||
st.Value(&structA).OldValue(&structA).ExpectValid()
|
||||
|
||||
st.Value(&structA).OldValue(&structB).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByDetailSubstring().ByOrigin(), field.ErrorList{
|
||||
field.Forbidden(field.NewPath("s"), ""),
|
||||
field.Forbidden(field.NewPath("i"), ""),
|
||||
field.Forbidden(field.NewPath("b"), ""),
|
||||
field.Forbidden(field.NewPath("f"), ""),
|
||||
field.Invalid(field.NewPath("s"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("i"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("b"), nil, "").WithOrigin("immutable"),
|
||||
field.Invalid(field.NewPath("f"), nil, "").WithOrigin("immutable"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -59,7 +59,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByCompare(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("s"), &obj.S, safe.Field(oldObj, func(oldObj *Struct) *string { return &oldObj.S }))...)
|
||||
|
||||
@@ -71,7 +74,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByCompare(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("i"), &obj.I, safe.Field(oldObj, func(oldObj *Struct) *int { return &oldObj.I }))...)
|
||||
|
||||
@@ -83,7 +89,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByCompare(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("b"), &obj.B, safe.Field(oldObj, func(oldObj *Struct) *bool { return &oldObj.B }))...)
|
||||
|
||||
@@ -95,7 +104,10 @@ func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field
|
||||
return nil
|
||||
}
|
||||
// call field-attached validations
|
||||
errs = append(errs, validate.ImmutableByCompare(ctx, op, fldPath, obj, oldObj)...)
|
||||
if e := validate.Immutable(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
errs = append(errs, e...)
|
||||
return // do not proceed
|
||||
}
|
||||
return
|
||||
}(fldPath.Child("f"), &obj.F, safe.Field(oldObj, func(oldObj *Struct) *float64 { return &oldObj.F }))...)
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ package validators
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/code-generator/cmd/validation-gen/util"
|
||||
"k8s.io/gengo/v2/codetags"
|
||||
"k8s.io/gengo/v2/types"
|
||||
)
|
||||
@@ -46,24 +45,14 @@ func (immutableTagValidator) ValidScopes() sets.Set[Scope] {
|
||||
}
|
||||
|
||||
var (
|
||||
immutableCompareValidator = types.Name{Package: libValidationPkg, Name: "ImmutableByCompare"}
|
||||
immutableReflectValidator = types.Name{Package: libValidationPkg, Name: "ImmutableByReflect"}
|
||||
immutableValidator = types.Name{Package: libValidationPkg, Name: "Immutable"}
|
||||
)
|
||||
|
||||
func (immutableTagValidator) GetValidations(context Context, _ codetags.Tag) (Validations, error) {
|
||||
var result Validations
|
||||
|
||||
if util.IsDirectComparable(util.NonPointer(util.NativeType(context.Type))) {
|
||||
// This is a minor optimization to just compare primitive values when
|
||||
// possible. Slices and maps are not comparable, and structs might hold
|
||||
// pointer fields, which are directly comparable but not what we need.
|
||||
//
|
||||
// Note: This compares the pointee, not the pointer itself.
|
||||
result.AddFunction(Function(immutableTagName, DefaultFlags, immutableCompareValidator))
|
||||
} else {
|
||||
result.AddFunction(Function(immutableTagName, DefaultFlags, immutableReflectValidator))
|
||||
}
|
||||
|
||||
// Use ShortCircuit flag so immutable runs in the same group as +k8s:optional.
|
||||
result.AddFunction(Function(immutableTagName, ShortCircuit, immutableValidator))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user