mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-18 04:54:54 +00:00
feedback: Address review comments
- Rename type params to Tstruct/Tfield/Tdisc for Subfield consistency - Make DiscriminatedRule.Value generic over Tdisc and emit typed literals - Move discriminator assignment adjacent to value assignment - Fix mockEqual to test value equivalence and add pointer test - Add test for value changed with discriminator unchanged - Add regex validation for discriminator group names - Disallow "default" as an explicit discriminator group name - Add directComparable cross-reference comment for ratcheting - Rename mode/modal terminology to discriminator/member
This commit is contained in:
@@ -18,47 +18,44 @@ package validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// ModalRule defines a validation to apply for a specific mode value.
|
||||
type ModalRule[T any] struct {
|
||||
Value string
|
||||
Validation ValidateFunc[T]
|
||||
// DiscriminatedRule defines a validation to apply for a specific discriminator value.
|
||||
type DiscriminatedRule[Tfield any, Tdisc comparable] struct {
|
||||
Value Tdisc
|
||||
Validation ValidateFunc[Tfield]
|
||||
}
|
||||
|
||||
// Modal validates a value based on a discriminator value.
|
||||
// Discriminated validates a member field based on a discriminator value.
|
||||
// It iterates through the rules and applies the first one that matches the discriminator.
|
||||
// If no rule matches, it applies the defaultValidation if provided.
|
||||
//
|
||||
// It performs ratcheting: if the operation is an Update, and neither the discriminator
|
||||
// nor the value (checked via equiv) have changed, validation is skipped.
|
||||
func Modal[T any, D comparable, P any](ctx context.Context, op operation.Operation, structPath *field.Path,
|
||||
obj, oldObj *P, fieldName string, getMemberValue func(*P) T, getDiscriminator func(*P) D,
|
||||
equiv MatchFunc[T], defaultValidation ValidateFunc[T], rules []ModalRule[T],
|
||||
func Discriminated[Tfield any, Tdisc comparable, Tstruct any](ctx context.Context, op operation.Operation, structPath *field.Path,
|
||||
obj, oldObj *Tstruct, fieldName string, getMemberValue func(*Tstruct) Tfield, getDiscriminator func(*Tstruct) Tdisc,
|
||||
equiv MatchFunc[Tfield], defaultValidation ValidateFunc[Tfield], rules []DiscriminatedRule[Tfield, Tdisc],
|
||||
) field.ErrorList {
|
||||
value := getMemberValue(obj)
|
||||
var oldValue T
|
||||
var oldDiscriminator D
|
||||
discriminator := getDiscriminator(obj)
|
||||
var oldValue Tfield
|
||||
var oldDiscriminator Tdisc
|
||||
|
||||
if oldObj != nil {
|
||||
oldValue = getMemberValue(oldObj)
|
||||
oldDiscriminator = getDiscriminator(oldObj)
|
||||
}
|
||||
|
||||
discriminator := getDiscriminator(obj)
|
||||
|
||||
if op.Type == operation.Update && oldObj != nil && discriminator == oldDiscriminator && equiv(value, oldValue) {
|
||||
return nil
|
||||
}
|
||||
|
||||
fldPath := structPath.Child(fieldName)
|
||||
dStr := fmt.Sprintf("%v", discriminator)
|
||||
for _, rule := range rules {
|
||||
if rule.Value == dStr {
|
||||
if rule.Value == discriminator {
|
||||
if rule.Validation == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -71,5 +68,4 @@ func Modal[T any, D comparable, P any](ctx context.Context, op operation.Operati
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes 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 validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func TestDiscriminated(t *testing.T) {
|
||||
errMatch := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "match error")}
|
||||
errDefault := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "default error")}
|
||||
|
||||
mockValid := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList {
|
||||
return nil
|
||||
}
|
||||
mockErrorMatch := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList {
|
||||
return errMatch
|
||||
}
|
||||
mockErrorDefault := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList {
|
||||
return errDefault
|
||||
}
|
||||
|
||||
// mockEqual compares pointer values by dereferencing, not by pointer identity.
|
||||
mockEqual := func(a, b *string) bool {
|
||||
if a == nil && b == nil {
|
||||
return true
|
||||
}
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
return *a == *b
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
opType operation.Type
|
||||
discriminator string
|
||||
oldDiscriminator string
|
||||
value *string
|
||||
oldValue *string
|
||||
rules []DiscriminatedRule[*string, string]
|
||||
defaultValidation ValidateFunc[*string]
|
||||
expected field.ErrorList
|
||||
}{
|
||||
{
|
||||
name: "matches rule, returns valid",
|
||||
opType: operation.Create,
|
||||
discriminator: "A",
|
||||
oldDiscriminator: "A",
|
||||
rules: []DiscriminatedRule[*string, string]{
|
||||
{Value: "A", Validation: mockValid},
|
||||
{Value: "B", Validation: mockErrorMatch},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "matches rule, returns error",
|
||||
opType: operation.Create,
|
||||
discriminator: "B",
|
||||
oldDiscriminator: "B",
|
||||
rules: []DiscriminatedRule[*string, string]{
|
||||
{Value: "A", Validation: mockValid},
|
||||
{Value: "B", Validation: mockErrorMatch},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: errMatch,
|
||||
},
|
||||
{
|
||||
name: "ratcheting: update, unchanged, skips validation",
|
||||
opType: operation.Update,
|
||||
discriminator: "B",
|
||||
oldDiscriminator: "B", // unchanged
|
||||
value: nil,
|
||||
oldValue: nil, // unchanged
|
||||
rules: []DiscriminatedRule[*string, string]{
|
||||
{Value: "B", Validation: mockErrorMatch}, // would fail if run
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "ratcheting: update, same value different pointers, skips validation",
|
||||
opType: operation.Update,
|
||||
discriminator: "B",
|
||||
oldDiscriminator: "B",
|
||||
value: strPtr("same"),
|
||||
oldValue: strPtr("same"), // different pointer, same value
|
||||
rules: []DiscriminatedRule[*string, string]{
|
||||
{Value: "B", Validation: mockErrorMatch}, // would fail if run
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "ratcheting: update, discriminator changed, runs validation",
|
||||
opType: operation.Update,
|
||||
discriminator: "B",
|
||||
oldDiscriminator: "A", // changed
|
||||
value: nil,
|
||||
oldValue: nil,
|
||||
rules: []DiscriminatedRule[*string, string]{
|
||||
{Value: "B", Validation: mockErrorMatch},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: errMatch,
|
||||
},
|
||||
{
|
||||
name: "ratcheting: update, value changed, discriminator unchanged, runs validation",
|
||||
opType: operation.Update,
|
||||
discriminator: "B",
|
||||
oldDiscriminator: "B", // unchanged
|
||||
value: strPtr("new"),
|
||||
oldValue: strPtr("old"), // changed
|
||||
rules: []DiscriminatedRule[*string, string]{
|
||||
{Value: "B", Validation: mockErrorMatch},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: errMatch,
|
||||
},
|
||||
{
|
||||
name: "matches rule with nil validation, returns valid",
|
||||
opType: operation.Create,
|
||||
discriminator: "A",
|
||||
rules: []DiscriminatedRule[*string, string]{
|
||||
{Value: "A", Validation: nil},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "no match, runs default",
|
||||
opType: operation.Create,
|
||||
discriminator: "C",
|
||||
rules: []DiscriminatedRule[*string, string]{
|
||||
{Value: "A", Validation: mockValid},
|
||||
{Value: "B", Validation: mockErrorMatch},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: errDefault,
|
||||
},
|
||||
{
|
||||
name: "no match, nil default, returns valid",
|
||||
opType: operation.Create,
|
||||
discriminator: "C",
|
||||
rules: []DiscriminatedRule[*string, string]{
|
||||
{Value: "A", Validation: mockValid},
|
||||
},
|
||||
defaultValidation: nil,
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
oldDisc := tc.oldDiscriminator
|
||||
if oldDisc == "" {
|
||||
oldDisc = tc.discriminator
|
||||
}
|
||||
|
||||
type StringP struct {
|
||||
Val *string
|
||||
Disc string
|
||||
}
|
||||
newObj := &StringP{Val: tc.value, Disc: tc.discriminator}
|
||||
var oldObj *StringP
|
||||
if tc.opType == operation.Update {
|
||||
oldObj = &StringP{Val: tc.oldValue, Disc: oldDisc}
|
||||
}
|
||||
getVal := func(p *StringP) *string { return p.Val }
|
||||
getDisc := func(p *StringP) string { return p.Disc }
|
||||
got := Discriminated[*string, string, StringP](context.Background(), operation.Operation{Type: tc.opType}, field.NewPath("root"), newObj, oldObj, "field", getVal, getDisc, mockEqual, tc.defaultValidation, tc.rules)
|
||||
|
||||
if !reflect.DeepEqual(got, tc.expected) {
|
||||
t.Errorf("got %v want %v", got, tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscriminatedIntDiscriminator(t *testing.T) {
|
||||
errMatch := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "match error")}
|
||||
errDefault := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "default error")}
|
||||
|
||||
mockErrorMatch := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList {
|
||||
return errMatch
|
||||
}
|
||||
mockErrorDefault := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList {
|
||||
return errDefault
|
||||
}
|
||||
|
||||
mockEqual := func(a, b *string) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
type IntP struct {
|
||||
Val *string
|
||||
Disc int
|
||||
}
|
||||
newObj := &IntP{Val: nil, Disc: 1}
|
||||
getVal := func(p *IntP) *string { return p.Val }
|
||||
getDisc := func(p *IntP) int { return p.Disc }
|
||||
|
||||
got := Discriminated[*string, int, IntP](context.Background(), operation.Operation{Type: operation.Create}, field.NewPath("root"), newObj, nil, "field", getVal, getDisc, mockEqual, mockErrorDefault, []DiscriminatedRule[*string, int]{
|
||||
{Value: 1, Validation: mockErrorMatch},
|
||||
})
|
||||
if !reflect.DeepEqual(got, errMatch) {
|
||||
t.Errorf("int discriminator: got %v want %v", got, errMatch)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiscriminatedBoolDiscriminator(t *testing.T) {
|
||||
errMatch := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "match error")}
|
||||
errDefault := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "default error")}
|
||||
|
||||
mockErrorMatch := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList {
|
||||
return errMatch
|
||||
}
|
||||
mockErrorDefault := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList {
|
||||
return errDefault
|
||||
}
|
||||
|
||||
mockEqual := func(a, b *string) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
type BoolP struct {
|
||||
Val *string
|
||||
Disc bool
|
||||
}
|
||||
newObj := &BoolP{Val: nil, Disc: true}
|
||||
getVal := func(p *BoolP) *string { return p.Val }
|
||||
getDisc := func(p *BoolP) bool { return p.Disc }
|
||||
|
||||
got := Discriminated[*string, bool, BoolP](context.Background(), operation.Operation{Type: operation.Create}, field.NewPath("root"), newObj, nil, "field", getVal, getDisc, mockEqual, mockErrorDefault, []DiscriminatedRule[*string, bool]{
|
||||
{Value: true, Validation: mockErrorMatch},
|
||||
})
|
||||
if !reflect.DeepEqual(got, errMatch) {
|
||||
t.Errorf("bool discriminator: got %v want %v", got, errMatch)
|
||||
}
|
||||
}
|
||||
|
||||
// strPtr returns a new pointer to a copy of s, guaranteeing a distinct allocation.
|
||||
func strPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
/*
|
||||
Copyright 2025 The Kubernetes 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 validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func TestModal(t *testing.T) {
|
||||
errMatch := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "match error")}
|
||||
errDefault := field.ErrorList{field.Invalid(field.NewPath("foo"), "bar", "default error")}
|
||||
|
||||
mockValid := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList {
|
||||
return nil
|
||||
}
|
||||
mockErrorMatch := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList {
|
||||
return errMatch
|
||||
}
|
||||
mockErrorDefault := func(_ context.Context, _ operation.Operation, _ *field.Path, _, _ *string) field.ErrorList {
|
||||
return errDefault
|
||||
}
|
||||
|
||||
mockEqual := func(a, b *string) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
opType operation.Type
|
||||
discriminator any
|
||||
oldDiscriminator any
|
||||
value *string
|
||||
oldValue *string
|
||||
rules []ModalRule[*string]
|
||||
defaultValidation ValidateFunc[*string]
|
||||
expected field.ErrorList
|
||||
}{
|
||||
{
|
||||
name: "matches rule, returns valid",
|
||||
opType: operation.Create,
|
||||
discriminator: "A",
|
||||
oldDiscriminator: "A",
|
||||
rules: []ModalRule[*string]{
|
||||
{Value: "A", Validation: mockValid},
|
||||
{Value: "B", Validation: mockErrorMatch},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "matches rule, returns error",
|
||||
opType: operation.Create,
|
||||
discriminator: "B",
|
||||
oldDiscriminator: "B",
|
||||
rules: []ModalRule[*string]{
|
||||
{Value: "A", Validation: mockValid},
|
||||
{Value: "B", Validation: mockErrorMatch},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: errMatch,
|
||||
},
|
||||
{
|
||||
name: "ratcheting: update, unchanged, skips validation",
|
||||
opType: operation.Update,
|
||||
discriminator: "B",
|
||||
oldDiscriminator: "B", // unchanged
|
||||
value: nil,
|
||||
oldValue: nil, // unchanged
|
||||
rules: []ModalRule[*string]{
|
||||
{Value: "B", Validation: mockErrorMatch}, // would fail if run
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "ratcheting: update, discriminator changed, runs validation",
|
||||
opType: operation.Update,
|
||||
discriminator: "B",
|
||||
oldDiscriminator: "A", // changed
|
||||
value: nil,
|
||||
oldValue: nil,
|
||||
rules: []ModalRule[*string]{
|
||||
{Value: "B", Validation: mockErrorMatch},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: errMatch,
|
||||
},
|
||||
{
|
||||
name: "matches rule with nil validation, returns valid",
|
||||
opType: operation.Create,
|
||||
discriminator: "A",
|
||||
rules: []ModalRule[*string]{
|
||||
{Value: "A", Validation: nil},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "no match, runs default",
|
||||
opType: operation.Create,
|
||||
discriminator: "C",
|
||||
rules: []ModalRule[*string]{
|
||||
{Value: "A", Validation: mockValid},
|
||||
{Value: "B", Validation: mockErrorMatch},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: errDefault,
|
||||
},
|
||||
{
|
||||
name: "no match, nil default, returns valid",
|
||||
opType: operation.Create,
|
||||
discriminator: "C",
|
||||
rules: []ModalRule[*string]{
|
||||
{Value: "A", Validation: mockValid},
|
||||
},
|
||||
defaultValidation: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "int discriminator matches",
|
||||
opType: operation.Create,
|
||||
discriminator: 1,
|
||||
rules: []ModalRule[*string]{
|
||||
{Value: "1", Validation: mockErrorMatch},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: errMatch,
|
||||
},
|
||||
{
|
||||
name: "bool discriminator matches",
|
||||
opType: operation.Create,
|
||||
discriminator: true,
|
||||
rules: []ModalRule[*string]{
|
||||
{Value: "true", Validation: mockErrorMatch},
|
||||
},
|
||||
defaultValidation: mockErrorDefault,
|
||||
expected: errMatch,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Handle nil oldDiscriminator for tests that don't specify it
|
||||
oldDisc := tc.oldDiscriminator
|
||||
if oldDisc == nil {
|
||||
oldDisc = tc.discriminator
|
||||
}
|
||||
|
||||
// We need separate Modal calls because D is different per test case
|
||||
var got field.ErrorList
|
||||
|
||||
switch d := tc.discriminator.(type) {
|
||||
case int:
|
||||
od, _ := oldDisc.(int)
|
||||
type IntP struct {
|
||||
Val *string
|
||||
Disc int
|
||||
}
|
||||
newObj := &IntP{Val: tc.value, Disc: d}
|
||||
var oldObj *IntP
|
||||
if tc.opType == operation.Update {
|
||||
oldObj = &IntP{Val: tc.oldValue, Disc: od}
|
||||
}
|
||||
getVal := func(p *IntP) *string { return p.Val }
|
||||
getDisc := func(p *IntP) int { return p.Disc }
|
||||
|
||||
got = Modal[*string, int, IntP](context.Background(), operation.Operation{Type: tc.opType}, field.NewPath("root"), newObj, oldObj, "field", getVal, getDisc, mockEqual, tc.defaultValidation, tc.rules)
|
||||
|
||||
case bool:
|
||||
od, _ := oldDisc.(bool)
|
||||
type BoolP struct {
|
||||
Val *string
|
||||
Disc bool
|
||||
}
|
||||
newObj := &BoolP{Val: tc.value, Disc: d}
|
||||
var oldObj *BoolP
|
||||
if tc.opType == operation.Update {
|
||||
oldObj = &BoolP{Val: tc.oldValue, Disc: od}
|
||||
}
|
||||
getVal := func(p *BoolP) *string { return p.Val }
|
||||
getDisc := func(p *BoolP) bool { return p.Disc }
|
||||
got = Modal[*string, bool, BoolP](context.Background(), operation.Operation{Type: tc.opType}, field.NewPath("root"), newObj, oldObj, "field", getVal, getDisc, mockEqual, tc.defaultValidation, tc.rules)
|
||||
|
||||
case string:
|
||||
od, _ := oldDisc.(string)
|
||||
type StringP struct {
|
||||
Val *string
|
||||
Disc string
|
||||
}
|
||||
newObj := &StringP{Val: tc.value, Disc: d}
|
||||
var oldObj *StringP
|
||||
if tc.opType == operation.Update {
|
||||
oldObj = &StringP{Val: tc.oldValue, Disc: od}
|
||||
}
|
||||
getVal := func(p *StringP) *string { return p.Val }
|
||||
getDisc := func(p *StringP) string { return p.Disc }
|
||||
got = Modal[*string, string, StringP](context.Background(), operation.Operation{Type: tc.opType}, field.NewPath("root"), newObj, oldObj, "field", getVal, getDisc, mockEqual, tc.defaultValidation, tc.rules)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tc.expected) {
|
||||
t.Errorf("got %v want %v", got, tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ limitations under the License.
|
||||
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
|
||||
|
||||
// This is a test package.
|
||||
package mode
|
||||
package discriminator
|
||||
|
||||
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package mode
|
||||
package discriminator
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -19,7 +19,7 @@ limitations under the License.
|
||||
|
||||
// Code generated by validation-gen. DO NOT EDIT.
|
||||
|
||||
package mode
|
||||
package discriminator
|
||||
|
||||
import (
|
||||
context "context"
|
||||
@@ -99,14 +99,13 @@ func RegisterValidations(scheme *testscheme.Scheme) error {
|
||||
// Validate_ChainedValidation validates an instance of ChainedValidation according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_ChainedValidation(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ChainedValidation) (errs field.ErrorList) {
|
||||
errs = append(errs, validate.Modal(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *ChainedValidation) *string { return obj.FieldA }, func(obj *ChainedValidation) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *ChainedValidation) *string { return obj.FieldA }, func(obj *ChainedValidation) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...)
|
||||
return errs
|
||||
}, []validate.ModalRule[*string]{
|
||||
}, []validate.DiscriminatedRule[*string, string]{
|
||||
{
|
||||
Value: "A",
|
||||
Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
earlyReturn := false
|
||||
if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
@@ -118,8 +117,7 @@ func Validate_ChainedValidation(ctx context.Context, op operation.Operation, fld
|
||||
}
|
||||
errs = append(errs, validate.MaxLength(ctx, op, fldPath, obj, oldObj, 5)...)
|
||||
return errs
|
||||
},
|
||||
},
|
||||
}},
|
||||
})...)
|
||||
|
||||
// field ChainedValidation.TypeMeta has no validation
|
||||
@@ -131,14 +129,13 @@ func Validate_ChainedValidation(ctx context.Context, op operation.Operation, fld
|
||||
// Validate_Collections validates an instance of Collections according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_Collections(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Collections) (errs field.ErrorList) {
|
||||
errs = append(errs, validate.Modal(ctx, op, fldPath, obj, oldObj, "listField", func(obj *Collections) []string { return obj.ListField }, func(obj *Collections) string { return obj.D1 }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList {
|
||||
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "listField", func(obj *Collections) []string { return obj.ListField }, func(obj *Collections) string { return obj.D1 }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
errs = append(errs, validate.ForbiddenSlice(ctx, op, fldPath, obj, oldObj)...)
|
||||
return errs
|
||||
}, []validate.ModalRule[[]string]{
|
||||
}, []validate.DiscriminatedRule[[]string, string]{
|
||||
{
|
||||
Value: "A",
|
||||
Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList {
|
||||
Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj []string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
earlyReturn := false
|
||||
if e := validate.OptionalSlice(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
@@ -148,17 +145,15 @@ func Validate_Collections(ctx context.Context, op operation.Operation, fldPath *
|
||||
return errs
|
||||
}
|
||||
return errs
|
||||
},
|
||||
},
|
||||
}},
|
||||
})...)
|
||||
errs = append(errs, validate.Modal(ctx, op, fldPath, obj, oldObj, "mapField", func(obj *Collections) map[string]string { return obj.MapField }, func(obj *Collections) string { return obj.D1 }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList {
|
||||
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "mapField", func(obj *Collections) map[string]string { return obj.MapField }, func(obj *Collections) string { return obj.D1 }, validate.SemanticDeepEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
errs = append(errs, validate.ForbiddenMap(ctx, op, fldPath, obj, oldObj)...)
|
||||
return errs
|
||||
}, []validate.ModalRule[map[string]string]{
|
||||
}, []validate.DiscriminatedRule[map[string]string, string]{
|
||||
{
|
||||
Value: "A",
|
||||
Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList {
|
||||
Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj map[string]string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
earlyReturn := false
|
||||
if e := validate.OptionalMap(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
@@ -168,8 +163,7 @@ func Validate_Collections(ctx context.Context, op operation.Operation, fldPath *
|
||||
return errs
|
||||
}
|
||||
return errs
|
||||
},
|
||||
},
|
||||
}},
|
||||
})...)
|
||||
|
||||
// field Collections.TypeMeta has no validation
|
||||
@@ -182,14 +176,13 @@ func Validate_Collections(ctx context.Context, op operation.Operation, fldPath *
|
||||
// Validate_ImplicitForbidden validates an instance of ImplicitForbidden according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_ImplicitForbidden(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *ImplicitForbidden) (errs field.ErrorList) {
|
||||
errs = append(errs, validate.Modal(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *ImplicitForbidden) *string { return obj.FieldA }, func(obj *ImplicitForbidden) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *ImplicitForbidden) *string { return obj.FieldA }, func(obj *ImplicitForbidden) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...)
|
||||
return errs
|
||||
}, []validate.ModalRule[*string]{
|
||||
}, []validate.DiscriminatedRule[*string, string]{
|
||||
{
|
||||
Value: "A",
|
||||
Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
earlyReturn := false
|
||||
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
@@ -199,8 +192,7 @@ func Validate_ImplicitForbidden(ctx context.Context, op operation.Operation, fld
|
||||
return errs
|
||||
}
|
||||
return errs
|
||||
},
|
||||
},
|
||||
}},
|
||||
})...)
|
||||
|
||||
// field ImplicitForbidden.TypeMeta has no validation
|
||||
@@ -212,14 +204,13 @@ func Validate_ImplicitForbidden(ctx context.Context, op operation.Operation, fld
|
||||
// Validate_MultipleDiscriminators validates an instance of MultipleDiscriminators according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_MultipleDiscriminators(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MultipleDiscriminators) (errs field.ErrorList) {
|
||||
errs = append(errs, validate.Modal(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *MultipleDiscriminators) *string { return obj.FieldA }, func(obj *MultipleDiscriminators) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *MultipleDiscriminators) *string { return obj.FieldA }, func(obj *MultipleDiscriminators) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...)
|
||||
return errs
|
||||
}, []validate.ModalRule[*string]{
|
||||
}, []validate.DiscriminatedRule[*string, string]{
|
||||
{
|
||||
Value: "A",
|
||||
Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
earlyReturn := false
|
||||
if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
@@ -230,17 +221,15 @@ func Validate_MultipleDiscriminators(ctx context.Context, op operation.Operation
|
||||
return errs
|
||||
}
|
||||
return errs
|
||||
},
|
||||
},
|
||||
}},
|
||||
})...)
|
||||
errs = append(errs, validate.Modal(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *MultipleDiscriminators) *string { return obj.FieldB }, func(obj *MultipleDiscriminators) string { return obj.D2 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *MultipleDiscriminators) *string { return obj.FieldB }, func(obj *MultipleDiscriminators) string { return obj.D2 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...)
|
||||
return errs
|
||||
}, []validate.ModalRule[*string]{
|
||||
}, []validate.DiscriminatedRule[*string, string]{
|
||||
{
|
||||
Value: "B",
|
||||
Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
Value: "B", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
earlyReturn := false
|
||||
if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
@@ -251,8 +240,7 @@ func Validate_MultipleDiscriminators(ctx context.Context, op operation.Operation
|
||||
return errs
|
||||
}
|
||||
return errs
|
||||
},
|
||||
},
|
||||
}},
|
||||
})...)
|
||||
|
||||
// field MultipleDiscriminators.TypeMeta has no validation
|
||||
@@ -266,14 +254,13 @@ func Validate_MultipleDiscriminators(ctx context.Context, op operation.Operation
|
||||
// Validate_NonStringDiscriminator validates an instance of NonStringDiscriminator according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_NonStringDiscriminator(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *NonStringDiscriminator) (errs field.ErrorList) {
|
||||
errs = append(errs, validate.Modal(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *NonStringDiscriminator) *string { return obj.FieldA }, func(obj *NonStringDiscriminator) bool { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *NonStringDiscriminator) *string { return obj.FieldA }, func(obj *NonStringDiscriminator) bool { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...)
|
||||
return errs
|
||||
}, []validate.ModalRule[*string]{
|
||||
}, []validate.DiscriminatedRule[*string, bool]{
|
||||
{
|
||||
Value: "true",
|
||||
Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
Value: true, Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
earlyReturn := false
|
||||
if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
@@ -284,17 +271,15 @@ func Validate_NonStringDiscriminator(ctx context.Context, op operation.Operation
|
||||
return errs
|
||||
}
|
||||
return errs
|
||||
},
|
||||
},
|
||||
}},
|
||||
})...)
|
||||
errs = append(errs, validate.Modal(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *NonStringDiscriminator) *string { return obj.FieldB }, func(obj *NonStringDiscriminator) int { return obj.D2 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *NonStringDiscriminator) *string { return obj.FieldB }, func(obj *NonStringDiscriminator) int { return obj.D2 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...)
|
||||
return errs
|
||||
}, []validate.ModalRule[*string]{
|
||||
}, []validate.DiscriminatedRule[*string, int]{
|
||||
{
|
||||
Value: "1",
|
||||
Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
Value: 1, Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
earlyReturn := false
|
||||
if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
@@ -305,8 +290,7 @@ func Validate_NonStringDiscriminator(ctx context.Context, op operation.Operation
|
||||
return errs
|
||||
}
|
||||
return errs
|
||||
},
|
||||
},
|
||||
}},
|
||||
})...)
|
||||
|
||||
// field NonStringDiscriminator.TypeMeta has no validation
|
||||
@@ -320,14 +304,13 @@ func Validate_NonStringDiscriminator(ctx context.Context, op operation.Operation
|
||||
// Validate_SharedField validates an instance of SharedField according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_SharedField(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SharedField) (errs field.ErrorList) {
|
||||
errs = append(errs, validate.Modal(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *SharedField) *string { return obj.FieldA }, func(obj *SharedField) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *SharedField) *string { return obj.FieldA }, func(obj *SharedField) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...)
|
||||
return errs
|
||||
}, []validate.ModalRule[*string]{
|
||||
}, []validate.DiscriminatedRule[*string, string]{
|
||||
{
|
||||
Value: "A",
|
||||
Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
earlyReturn := false
|
||||
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
@@ -337,11 +320,9 @@ func Validate_SharedField(ctx context.Context, op operation.Operation, fldPath *
|
||||
return errs
|
||||
}
|
||||
return errs
|
||||
},
|
||||
},
|
||||
}},
|
||||
{
|
||||
Value: "B",
|
||||
Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
Value: "B", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
earlyReturn := false
|
||||
if e := validate.OptionalPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
@@ -351,8 +332,7 @@ func Validate_SharedField(ctx context.Context, op operation.Operation, fldPath *
|
||||
return errs
|
||||
}
|
||||
return errs
|
||||
},
|
||||
},
|
||||
}},
|
||||
})...)
|
||||
|
||||
// field SharedField.TypeMeta has no validation
|
||||
@@ -364,14 +344,13 @@ func Validate_SharedField(ctx context.Context, op operation.Operation, fldPath *
|
||||
// Validate_StrictUnion validates an instance of StrictUnion according
|
||||
// to declarative validation rules in the API schema.
|
||||
func Validate_StrictUnion(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *StrictUnion) (errs field.ErrorList) {
|
||||
errs = append(errs, validate.Modal(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *StrictUnion) *string { return obj.FieldA }, func(obj *StrictUnion) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *StrictUnion) *string { return obj.FieldA }, func(obj *StrictUnion) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...)
|
||||
return errs
|
||||
}, []validate.ModalRule[*string]{
|
||||
}, []validate.DiscriminatedRule[*string, string]{
|
||||
{
|
||||
Value: "A",
|
||||
Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
Value: "A", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
earlyReturn := false
|
||||
if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
@@ -382,17 +361,15 @@ func Validate_StrictUnion(ctx context.Context, op operation.Operation, fldPath *
|
||||
return errs
|
||||
}
|
||||
return errs
|
||||
},
|
||||
},
|
||||
}},
|
||||
})...)
|
||||
errs = append(errs, validate.Modal(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *StrictUnion) *string { return obj.FieldB }, func(obj *StrictUnion) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *StrictUnion) *string { return obj.FieldB }, func(obj *StrictUnion) string { return obj.D1 }, validate.DirectEqualPtr, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
errs = append(errs, validate.ForbiddenPointer(ctx, op, fldPath, obj, oldObj)...)
|
||||
return errs
|
||||
}, []validate.ModalRule[*string]{
|
||||
}, []validate.DiscriminatedRule[*string, string]{
|
||||
{
|
||||
Value: "B",
|
||||
Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
Value: "B", Validation: func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *string) field.ErrorList {
|
||||
errs := field.ErrorList{}
|
||||
earlyReturn := false
|
||||
if e := validate.RequiredPointer(ctx, op, fldPath, obj, oldObj); len(e) != 0 {
|
||||
@@ -403,8 +380,7 @@ func Validate_StrictUnion(ctx context.Context, op operation.Operation, fldPath *
|
||||
return errs
|
||||
}
|
||||
return errs
|
||||
},
|
||||
},
|
||||
}},
|
||||
})...)
|
||||
|
||||
// field StrictUnion.TypeMeta has no validation
|
||||
@@ -18,7 +18,9 @@ package validators
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/code-generator/cmd/validation-gen/util"
|
||||
@@ -32,66 +34,70 @@ const (
|
||||
memberTagName = "k8s:member"
|
||||
)
|
||||
|
||||
// validGroupNameRegex restricts discriminator group names to identifiers that
|
||||
// start with a letter and contain only alphanumeric characters and underscores.
|
||||
var validGroupNameRegex = regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_]*$`)
|
||||
|
||||
func init() {
|
||||
RegisterTagValidator(&modeTagValidator{modeDefinitions})
|
||||
RegisterTagValidator(&memberTagValidator{modeDefinitions, nil})
|
||||
RegisterTypeValidator(&modeTypeOrFieldValidator{modeDefinitions})
|
||||
RegisterFieldValidator(&modeTypeOrFieldValidator{modeDefinitions})
|
||||
RegisterTagValidator(&discriminatorTagValidator{discriminatorDefinitions})
|
||||
RegisterTagValidator(&memberTagValidator{discriminatorDefinitions, nil})
|
||||
RegisterTypeValidator(&discriminatorFieldValidator{discriminatorDefinitions})
|
||||
RegisterFieldValidator(&discriminatorFieldValidator{discriminatorDefinitions})
|
||||
}
|
||||
|
||||
// modeDefinitions stores all mode definitions found by tag validators.
|
||||
// discriminatorDefinitions stores all discriminator definitions found by tag validators.
|
||||
// Key is the struct path.
|
||||
var modeDefinitions = map[string]modeGroups{}
|
||||
var discriminatorDefinitions = map[string]discriminatorGroups{}
|
||||
|
||||
type modeGroups map[string]*modeGroup
|
||||
type discriminatorGroups map[string]*discriminatorGroup
|
||||
|
||||
type modeGroup struct {
|
||||
type discriminatorGroup struct {
|
||||
name string
|
||||
discriminatorMember *types.Member
|
||||
// members maps field names to their rules in this mode group.
|
||||
members map[string]*fieldModeRules
|
||||
// members maps field names to their rules in this discriminator group.
|
||||
members map[string]*fieldMemberRules
|
||||
}
|
||||
|
||||
type fieldModeRules struct {
|
||||
type fieldMemberRules struct {
|
||||
member *types.Member
|
||||
rules []modeRule
|
||||
rules []memberRule
|
||||
}
|
||||
|
||||
type modeRule struct {
|
||||
type memberRule struct {
|
||||
value string
|
||||
validations Validations
|
||||
}
|
||||
|
||||
func (mg modeGroups) getOrCreate(name string) *modeGroup {
|
||||
func (mg discriminatorGroups) getOrCreate(name string) *discriminatorGroup {
|
||||
if name == "" {
|
||||
name = "default"
|
||||
}
|
||||
g, ok := mg[name]
|
||||
if !ok {
|
||||
g = &modeGroup{
|
||||
g = &discriminatorGroup{
|
||||
name: name,
|
||||
members: make(map[string]*fieldModeRules),
|
||||
members: make(map[string]*fieldMemberRules),
|
||||
}
|
||||
mg[name] = g
|
||||
}
|
||||
return g
|
||||
}
|
||||
|
||||
type modeTagValidator struct {
|
||||
shared map[string]modeGroups
|
||||
type discriminatorTagValidator struct {
|
||||
shared map[string]discriminatorGroups
|
||||
}
|
||||
|
||||
func (mtv *modeTagValidator) Init(_ Config) {}
|
||||
func (mtv *discriminatorTagValidator) Init(_ Config) {}
|
||||
|
||||
func (mtv *modeTagValidator) TagName() string {
|
||||
func (mtv *discriminatorTagValidator) TagName() string {
|
||||
return discriminatorTagName
|
||||
}
|
||||
|
||||
func (mtv *modeTagValidator) ValidScopes() sets.Set[Scope] {
|
||||
func (mtv *discriminatorTagValidator) ValidScopes() sets.Set[Scope] {
|
||||
return sets.New(ScopeField)
|
||||
}
|
||||
|
||||
func (mtv *modeTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) {
|
||||
func (mtv *discriminatorTagValidator) GetValidations(context Context, tag codetags.Tag) (Validations, error) {
|
||||
if util.NativeType(context.Type).Kind == types.Pointer {
|
||||
return Validations{}, fmt.Errorf("can only be used on non-pointer types")
|
||||
}
|
||||
@@ -101,22 +107,28 @@ func (mtv *modeTagValidator) GetValidations(context Context, tag codetags.Tag) (
|
||||
}
|
||||
|
||||
if mtv.shared[context.ParentPath.String()] == nil {
|
||||
mtv.shared[context.ParentPath.String()] = make(modeGroups)
|
||||
mtv.shared[context.ParentPath.String()] = make(discriminatorGroups)
|
||||
}
|
||||
modeName := ""
|
||||
groupName := ""
|
||||
if nameArg, ok := tag.NamedArg("name"); ok {
|
||||
modeName = nameArg.Value
|
||||
groupName = nameArg.Value
|
||||
}
|
||||
group := mtv.shared[context.ParentPath.String()].getOrCreate(modeName)
|
||||
if groupName != "" && !validGroupNameRegex.MatchString(groupName) {
|
||||
return Validations{}, fmt.Errorf("discriminator group name must match %s, got %q", validGroupNameRegex.String(), groupName)
|
||||
}
|
||||
if groupName == "default" {
|
||||
return Validations{}, fmt.Errorf("discriminator group name %q is reserved", groupName)
|
||||
}
|
||||
group := mtv.shared[context.ParentPath.String()].getOrCreate(groupName)
|
||||
if group.discriminatorMember != nil && group.discriminatorMember != context.Member {
|
||||
return Validations{}, fmt.Errorf("duplicate discriminator: %q", modeName)
|
||||
return Validations{}, fmt.Errorf("duplicate discriminator: %q", groupName)
|
||||
}
|
||||
group.discriminatorMember = context.Member
|
||||
|
||||
return Validations{}, nil
|
||||
}
|
||||
|
||||
func (mtv *modeTagValidator) Docs() TagDoc {
|
||||
func (mtv *discriminatorTagValidator) Docs() TagDoc {
|
||||
return TagDoc{
|
||||
Tag: mtv.TagName(),
|
||||
StabilityLevel: TagStabilityLevelAlpha,
|
||||
@@ -132,7 +144,7 @@ func (mtv *modeTagValidator) Docs() TagDoc {
|
||||
}
|
||||
|
||||
type memberTagValidator struct {
|
||||
shared map[string]modeGroups
|
||||
shared map[string]discriminatorGroups
|
||||
validator TagValidationExtractor
|
||||
}
|
||||
|
||||
@@ -167,9 +179,12 @@ func (mtv *memberTagValidator) GetValidations(context Context, tag codetags.Tag)
|
||||
return Validations{}, fmt.Errorf("unsupported payload tag: %q", tag.ValueTag.Name)
|
||||
}
|
||||
|
||||
modeName := ""
|
||||
groupName := ""
|
||||
if modeArg, ok := tag.NamedArg("discriminator"); ok {
|
||||
modeName = modeArg.Value
|
||||
groupName = modeArg.Value
|
||||
}
|
||||
if groupName == "default" {
|
||||
return Validations{}, fmt.Errorf("discriminator group name %q is reserved", groupName)
|
||||
}
|
||||
|
||||
value := ""
|
||||
@@ -183,9 +198,9 @@ func (mtv *memberTagValidator) GetValidations(context Context, tag codetags.Tag)
|
||||
}
|
||||
|
||||
if mtv.shared[context.ParentPath.String()] == nil {
|
||||
mtv.shared[context.ParentPath.String()] = make(modeGroups)
|
||||
mtv.shared[context.ParentPath.String()] = make(discriminatorGroups)
|
||||
}
|
||||
group := mtv.shared[context.ParentPath.String()].getOrCreate(modeName)
|
||||
group := mtv.shared[context.ParentPath.String()].getOrCreate(groupName)
|
||||
|
||||
fieldName := context.Member.Name
|
||||
if rules, ok := group.members[fieldName]; ok {
|
||||
@@ -193,7 +208,7 @@ func (mtv *memberTagValidator) GetValidations(context Context, tag codetags.Tag)
|
||||
return Validations{}, fmt.Errorf("internal error: member mismatch for field %q", fieldName)
|
||||
}
|
||||
} else {
|
||||
group.members[fieldName] = &fieldModeRules{
|
||||
group.members[fieldName] = &fieldMemberRules{
|
||||
member: context.Member,
|
||||
}
|
||||
}
|
||||
@@ -203,7 +218,7 @@ func (mtv *memberTagValidator) GetValidations(context Context, tag codetags.Tag)
|
||||
return Validations{}, err
|
||||
}
|
||||
|
||||
group.members[fieldName].rules = append(group.members[fieldName].rules, modeRule{
|
||||
group.members[fieldName].rules = append(group.members[fieldName].rules, memberRule{
|
||||
value: value,
|
||||
validations: payloadValidations,
|
||||
})
|
||||
@@ -238,17 +253,17 @@ func (mtv *memberTagValidator) Docs() TagDoc {
|
||||
}
|
||||
}
|
||||
|
||||
type modeTypeOrFieldValidator struct {
|
||||
shared map[string]modeGroups
|
||||
type discriminatorFieldValidator struct {
|
||||
shared map[string]discriminatorGroups
|
||||
}
|
||||
|
||||
func (modeTypeOrFieldValidator) Init(_ Config) {}
|
||||
func (discriminatorFieldValidator) Init(_ Config) {}
|
||||
|
||||
func (modeTypeOrFieldValidator) Name() string {
|
||||
return "modeTypeOrFieldValidator"
|
||||
func (discriminatorFieldValidator) Name() string {
|
||||
return "discriminatorFieldValidator"
|
||||
}
|
||||
|
||||
func (mtfv *modeTypeOrFieldValidator) GetValidations(context Context) (Validations, error) {
|
||||
func (mtfv *discriminatorFieldValidator) GetValidations(context Context) (Validations, error) {
|
||||
// Extract the most concrete type possible.
|
||||
if k := util.NonPointer(util.NativeType(context.Type)).Kind; k != types.Struct {
|
||||
return Validations{}, nil
|
||||
@@ -288,7 +303,7 @@ func (mtfv *modeTypeOrFieldValidator) GetValidations(context Context) (Validatio
|
||||
|
||||
for _, fn := range fieldNames {
|
||||
rules := group.members[fn]
|
||||
v, err := mtfv.generateModeFieldValidation(context, group, rules)
|
||||
v, err := mtfv.generateMemberFieldValidation(context, group, rules)
|
||||
if err != nil {
|
||||
return Validations{}, err
|
||||
}
|
||||
@@ -299,7 +314,7 @@ func (mtfv *modeTypeOrFieldValidator) GetValidations(context Context) (Validatio
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (mtfv *modeTypeOrFieldValidator) generateModeFieldValidation(context Context, group *modeGroup, rules *fieldModeRules) (Validations, error) {
|
||||
func (mtfv *discriminatorFieldValidator) generateMemberFieldValidation(context Context, group *discriminatorGroup, rules *fieldMemberRules) (Validations, error) {
|
||||
fieldType := rules.member.Type
|
||||
|
||||
// Use the nilable form to handle missing values.
|
||||
@@ -322,7 +337,7 @@ func (mtfv *modeTypeOrFieldValidator) generateModeFieldValidation(context Contex
|
||||
return Validations{}, err
|
||||
}
|
||||
|
||||
// Prepare ModalRules
|
||||
// Prepare DiscriminatedRules
|
||||
// Aggregate rules by value
|
||||
rulesByValue := make(map[string]Validations)
|
||||
var values []string
|
||||
@@ -336,7 +351,9 @@ func (mtfv *modeTypeOrFieldValidator) generateModeFieldValidation(context Contex
|
||||
}
|
||||
slices.Sort(values)
|
||||
|
||||
var modalRules []any
|
||||
discriminatorType := group.discriminatorMember.Type
|
||||
|
||||
var discriminatedRules []any
|
||||
for _, val := range values {
|
||||
ruleValidations := rulesByValue[val]
|
||||
|
||||
@@ -345,22 +362,29 @@ func (mtfv *modeTypeOrFieldValidator) generateModeFieldValidation(context Contex
|
||||
ObjType: nilableFieldType,
|
||||
}
|
||||
|
||||
modalRules = append(modalRules, StructLiteral{
|
||||
Type: types.Name{Package: libValidationPkg, Name: "ModalRule"},
|
||||
TypeArgs: []*types.Type{nilableFieldType},
|
||||
// Convert the string tag value to the appropriate typed Go literal
|
||||
// for the discriminator type.
|
||||
typedValue, err := convertDiscriminatorValue(val, discriminatorType)
|
||||
if err != nil {
|
||||
return Validations{}, fmt.Errorf("invalid discriminator value %q: %w", val, err)
|
||||
}
|
||||
|
||||
discriminatedRules = append(discriminatedRules, StructLiteral{
|
||||
Type: types.Name{Package: libValidationPkg, Name: "DiscriminatedRule"},
|
||||
TypeArgs: []*types.Type{nilableFieldType, discriminatorType},
|
||||
Fields: []StructLiteralField{
|
||||
{Name: "Value", Value: val},
|
||||
{Name: "Value", Value: typedValue},
|
||||
{Name: "Validation", Value: wrapper},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
modalValidator := types.Name{Package: libValidationPkg, Name: "Modal"}
|
||||
discriminatedValidator := types.Name{Package: libValidationPkg, Name: "Discriminated"}
|
||||
|
||||
rulesSlice := SliceLiteral{
|
||||
ElementType: types.Name{Package: libValidationPkg, Name: "ModalRule"},
|
||||
ElementTypeArgs: []*types.Type{nilableFieldType},
|
||||
Elements: modalRules,
|
||||
ElementType: types.Name{Package: libValidationPkg, Name: "DiscriminatedRule"},
|
||||
ElementTypeArgs: []*types.Type{nilableFieldType, discriminatorType},
|
||||
Elements: discriminatedRules,
|
||||
}
|
||||
|
||||
// getValue extractor
|
||||
@@ -371,14 +395,15 @@ func (mtfv *modeTypeOrFieldValidator) generateModeFieldValidation(context Contex
|
||||
}
|
||||
|
||||
// getDiscriminator extractor
|
||||
discriminatorType := group.discriminatorMember.Type
|
||||
getDiscriminator := FunctionLiteral{
|
||||
Parameters: []ParamResult{{Name: "obj", Type: types.PointerTo(context.Type)}},
|
||||
Results: []ParamResult{{Type: discriminatorType}},
|
||||
Body: fmt.Sprintf("return obj.%s", group.discriminatorMember.Name),
|
||||
}
|
||||
|
||||
// Calculate equivArg for ratcheting the value
|
||||
// directComparable is used to determine whether we can use the direct
|
||||
// comparison operator "==" or need to use the semantic DeepEqual when
|
||||
// looking up and comparing correlated list elements for validation ratcheting.
|
||||
var equivArg any
|
||||
if util.IsDirectComparable(util.NonPointer(util.NativeType(fieldType))) {
|
||||
equivArg = Identifier(validateDirectEqualPtr)
|
||||
@@ -386,7 +411,7 @@ func (mtfv *modeTypeOrFieldValidator) generateModeFieldValidation(context Contex
|
||||
equivArg = Identifier(validateSemanticDeepEqual)
|
||||
}
|
||||
|
||||
fn := Function(discriminatorTagName, DefaultFlags, modalValidator,
|
||||
fn := Function(discriminatorTagName, DefaultFlags, discriminatedValidator,
|
||||
Literal(fmt.Sprintf("%q", jsonName)),
|
||||
getValue,
|
||||
getDiscriminator,
|
||||
@@ -398,7 +423,7 @@ func (mtfv *modeTypeOrFieldValidator) generateModeFieldValidation(context Contex
|
||||
return Validations{Functions: []FunctionGen{fn}}, nil
|
||||
}
|
||||
|
||||
func (mtfv *modeTypeOrFieldValidator) getForbiddenValidation(t *types.Type) (any, error) {
|
||||
func (mtfv *discriminatorFieldValidator) getForbiddenValidation(t *types.Type) (any, error) {
|
||||
var forbidden types.Name
|
||||
nt := util.NativeType(t)
|
||||
switch nt.Kind {
|
||||
@@ -409,7 +434,7 @@ func (mtfv *modeTypeOrFieldValidator) getForbiddenValidation(t *types.Type) (any
|
||||
case types.Pointer:
|
||||
forbidden = types.Name{Package: libValidationPkg, Name: "ForbiddenPointer"}
|
||||
case types.Struct:
|
||||
return nil, fmt.Errorf("modal member fields of struct type must be pointers")
|
||||
return nil, fmt.Errorf("discriminated member fields of struct type must be pointers")
|
||||
default:
|
||||
forbidden = types.Name{Package: libValidationPkg, Name: "ForbiddenValue"}
|
||||
}
|
||||
@@ -427,3 +452,32 @@ func (mtfv *modeTypeOrFieldValidator) getForbiddenValidation(t *types.Type) (any
|
||||
ObjType: wrapperObjType,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// convertDiscriminatorValue converts a string tag value to the appropriate
|
||||
// typed Go literal for the given discriminator type.
|
||||
func convertDiscriminatorValue(val string, discType *types.Type) (any, error) {
|
||||
nt := util.NonPointer(util.NativeType(discType))
|
||||
if nt.Kind != types.Builtin {
|
||||
return nil, fmt.Errorf("unsupported discriminator type: %s", nt.Name.Name)
|
||||
}
|
||||
|
||||
switch nt.Name.Name {
|
||||
case "string":
|
||||
return val, nil
|
||||
case "bool":
|
||||
b, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse %q as bool: %w", val, err)
|
||||
}
|
||||
return b, nil
|
||||
default:
|
||||
if types.IsInteger(nt) {
|
||||
i, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse %q as integer: %w", val, err)
|
||||
}
|
||||
return int(i), nil
|
||||
}
|
||||
return nil, fmt.Errorf("unsupported discriminator type: %s", nt.Name.Name)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user