Merge pull request #137682 from aaron-prindle/discriminator-and-member-stability-level-support

fix: stability level to discriminator member validations
This commit is contained in:
Kubernetes Prow Robot
2026-03-14 02:09:35 +05:30
committed by GitHub
4 changed files with 674 additions and 12 deletions

View File

@@ -0,0 +1,103 @@
/*
Copyright 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.
*/
// +k8s:validation-gen=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
// +k8s:validation-gen-nolint
package discriminators
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
var localSchemeBuilder = testscheme.New()
type AlphaStruct struct {
TypeMeta int
// +k8s:alpha=+k8s:discriminator
D1 string `json:"d1"`
// +k8s:alpha=+k8s:member("A")=+k8s:required
FieldA *string `json:"fieldA,omitempty"`
// +k8s:alpha=+k8s:member("B")=+k8s:required
FieldB *string `json:"fieldB,omitempty"`
}
type BetaStruct struct {
TypeMeta int
// +k8s:beta=+k8s:discriminator
D1 string `json:"d1"`
// +k8s:beta=+k8s:member("A")=+k8s:required
FieldA *string `json:"fieldA,omitempty"`
// +k8s:beta=+k8s:member("B")=+k8s:required
FieldB *string `json:"fieldB,omitempty"`
}
type MixedLevels struct {
TypeMeta int
// +k8s:discriminator
Mode string `json:"mode"`
// +k8s:alpha=+k8s:member("A")=+k8s:required
A *string `json:"a,omitempty"`
// +k8s:beta=+k8s:member("B")=+k8s:required
B *string `json:"b,omitempty"`
}
type CrossLevels struct {
TypeMeta int
// +k8s:beta=+k8s:discriminator
Kind string `json:"kind"`
// +k8s:alpha=+k8s:member("A")=+k8s:required
A *string `json:"a,omitempty"`
// +k8s:alpha=+k8s:member("B")=+k8s:required
B *string `json:"b,omitempty"`
}
type SameFieldMixed struct {
TypeMeta int
// +k8s:discriminator
Mode string `json:"mode"`
// +k8s:alpha=+k8s:member("A")=+k8s:required
// +k8s:beta=+k8s:member("B")=+k8s:required
Value *string `json:"value,omitempty"`
}
// SameValueMixedPayloads tests that multiple payload validations on the same
// discriminator value can have different stability levels.
type SameValueMixedPayloads struct {
TypeMeta int
// +k8s:discriminator
Mode string `json:"mode"`
// +k8s:alpha=+k8s:member("A")=+k8s:required
// +k8s:beta=+k8s:member("A")=+k8s:minLength=3
Value *string `json:"value,omitempty"`
}
type TypeMeta int

View File

@@ -0,0 +1,162 @@
/*
Copyright 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 discriminators
import (
"testing"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)
func TestAlpha(t *testing.T) {
st := localSchemeBuilder.Test(t)
// Valid: mode A with FieldA set
st.Value(&AlphaStruct{D1: "A", FieldA: ptr.To("val")}).ExpectValid()
// Invalid: mode A with FieldA missing (required), should be stability level alpha
st.Value(&AlphaStruct{D1: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("fieldA"), "").MarkAlpha(),
})
// Invalid: mode A with FieldB set (forbidden), should be stability level alpha
st.Value(&AlphaStruct{D1: "A", FieldA: ptr.To("val"), FieldB: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Forbidden(field.NewPath("fieldB"), "").MarkAlpha(),
})
// Valid: mode B with FieldB set
st.Value(&AlphaStruct{D1: "B", FieldB: ptr.To("val")}).ExpectValid()
// Invalid: mode B with FieldB missing (required), should be stability level alpha
st.Value(&AlphaStruct{D1: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("fieldB"), "").MarkAlpha(),
})
}
func TestBeta(t *testing.T) {
st := localSchemeBuilder.Test(t)
// Valid: mode A with FieldA set
st.Value(&BetaStruct{D1: "A", FieldA: ptr.To("val")}).ExpectValid()
// Invalid: mode A with FieldA missing (required), should be stability level beta
st.Value(&BetaStruct{D1: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("fieldA"), "").MarkBeta(),
})
// Invalid: mode A with FieldB set (forbidden), should be stability level beta
st.Value(&BetaStruct{D1: "A", FieldA: ptr.To("val"), FieldB: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Forbidden(field.NewPath("fieldB"), "").MarkBeta(),
})
// Valid: mode B with FieldB set
st.Value(&BetaStruct{D1: "B", FieldB: ptr.To("val")}).ExpectValid()
// Invalid: mode B with FieldB missing (required), should be stability level beta
st.Value(&BetaStruct{D1: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("fieldB"), "").MarkBeta(),
})
}
func TestMixedLevels(t *testing.T) {
st := localSchemeBuilder.Test(t)
// Valid cases
st.Value(&MixedLevels{Mode: "A", A: ptr.To("val")}).ExpectValid()
st.Value(&MixedLevels{Mode: "B", B: ptr.To("val")}).ExpectValid()
// Mode=A, missing A -> alpha error (field A is alpha-gated)
st.Value(&MixedLevels{Mode: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("a"), "").MarkAlpha(),
})
// Mode=B, missing B -> beta error (field B is beta-gated)
st.Value(&MixedLevels{Mode: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("b"), "").MarkBeta(),
})
// Mode=A with B set (forbidden) -> beta error (field B's +k8s:discriminator is beta)
st.Value(&MixedLevels{Mode: "A", A: ptr.To("val"), B: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Forbidden(field.NewPath("b"), "").MarkBeta(),
})
// Mode=B with A set (forbidden) -> alpha error (field A's +k8s:discriminator is alpha)
st.Value(&MixedLevels{Mode: "B", A: ptr.To("val"), B: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Forbidden(field.NewPath("a"), "").MarkAlpha(),
})
}
func TestCrossLevels(t *testing.T) {
st := localSchemeBuilder.Test(t)
// Valid cases
st.Value(&CrossLevels{Kind: "A", A: ptr.To("val")}).ExpectValid()
st.Value(&CrossLevels{Kind: "B", B: ptr.To("val")}).ExpectValid()
// Kind=A, missing A -> alpha error (field A is alpha-gated, discriminator is beta)
st.Value(&CrossLevels{Kind: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("a"), "").MarkAlpha(),
})
// Kind=B, missing B -> alpha error (field B is alpha-gated, discriminator is beta)
st.Value(&CrossLevels{Kind: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("b"), "").MarkAlpha(),
})
// Kind=A with B set (forbidden) -> alpha error (field B's member tag is alpha)
st.Value(&CrossLevels{Kind: "A", A: ptr.To("val"), B: ptr.To("val")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Forbidden(field.NewPath("b"), "").MarkAlpha(),
})
}
func TestSameFieldMixed(t *testing.T) {
st := localSchemeBuilder.Test(t)
// Valid cases
st.Value(&SameFieldMixed{Mode: "A", Value: ptr.To("val")}).ExpectValid()
st.Value(&SameFieldMixed{Mode: "B", Value: ptr.To("val")}).ExpectValid()
// Mode=A, missing Value -> alpha error (member("A") is alpha-gated)
st.Value(&SameFieldMixed{Mode: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("value"), "").MarkAlpha(),
})
// Mode=B, missing Value -> beta error (member("B") is beta-gated)
st.Value(&SameFieldMixed{Mode: "B"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("value"), "").MarkBeta(),
})
}
// TestSameValueMixedPayloads verifies that multiple payload validations on
// the same discriminator value can have different stability levels.
func TestSameValueMixedPayloads(t *testing.T) {
st := localSchemeBuilder.Test(t)
// Valid: Mode=A with Value set and long enough
st.Value(&SameValueMixedPayloads{Mode: "A", Value: ptr.To("abc")}).ExpectValid()
// Mode=A, missing Value -> alpha error (required is alpha-gated)
st.Value(&SameValueMixedPayloads{Mode: "A"}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.Required(field.NewPath("value"), "").MarkAlpha(),
})
// Mode=A, Value too short -> beta error (minLength is beta-gated)
st.Value(&SameValueMixedPayloads{Mode: "A", Value: ptr.To("ab")}).ExpectMatches(field.ErrorMatcher{}.ByType().ByField().ByValidationStabilityLevel(), field.ErrorList{
field.TooShort(field.NewPath("value"), "", 3).MarkBeta(),
})
}

View File

@@ -0,0 +1,357 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by validation-gen. DO NOT EDIT.
package discriminators
import (
context "context"
fmt "fmt"
operation "k8s.io/apimachinery/pkg/api/operation"
safe "k8s.io/apimachinery/pkg/api/safe"
validate "k8s.io/apimachinery/pkg/api/validate"
field "k8s.io/apimachinery/pkg/util/validation/field"
testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme"
)
func init() { localSchemeBuilder.Register(RegisterValidations) }
// RegisterValidations adds validation functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterValidations(scheme *testscheme.Scheme) error {
// type AlphaStruct
scheme.AddValidationFunc((*AlphaStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_AlphaStruct(ctx, op, nil /* fldPath */, obj.(*AlphaStruct), safe.Cast[*AlphaStruct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type BetaStruct
scheme.AddValidationFunc((*BetaStruct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_BetaStruct(ctx, op, nil /* fldPath */, obj.(*BetaStruct), safe.Cast[*BetaStruct](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type CrossLevels
scheme.AddValidationFunc((*CrossLevels)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_CrossLevels(ctx, op, nil /* fldPath */, obj.(*CrossLevels), safe.Cast[*CrossLevels](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type MixedLevels
scheme.AddValidationFunc((*MixedLevels)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_MixedLevels(ctx, op, nil /* fldPath */, obj.(*MixedLevels), safe.Cast[*MixedLevels](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type SameFieldMixed
scheme.AddValidationFunc((*SameFieldMixed)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_SameFieldMixed(ctx, op, nil /* fldPath */, obj.(*SameFieldMixed), safe.Cast[*SameFieldMixed](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
// type SameValueMixedPayloads
scheme.AddValidationFunc((*SameValueMixedPayloads)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
switch op.Request.SubresourcePath() {
case "/":
return Validate_SameValueMixedPayloads(ctx, op, nil /* fldPath */, obj.(*SameValueMixedPayloads), safe.Cast[*SameValueMixedPayloads](oldObj))
}
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
})
return nil
}
// Validate_AlphaStruct validates an instance of AlphaStruct according
// to declarative validation rules in the API schema.
func Validate_AlphaStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *AlphaStruct) (errs field.ErrorList) {
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *AlphaStruct) *string { return obj.FieldA }, func(obj *AlphaStruct) 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).MarkAlpha()...)
return errs
}, []validate.DiscriminatedRule[*string, string]{
{
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).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return errs
}
return errs
}},
})...)
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *AlphaStruct) *string { return obj.FieldB }, func(obj *AlphaStruct) 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).MarkAlpha()...)
return errs
}, []validate.DiscriminatedRule[*string, string]{
{
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).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return errs
}
return errs
}},
})...)
// field AlphaStruct.TypeMeta has no validation
// field AlphaStruct.D1 has no validation
// field AlphaStruct.FieldA has no validation
// field AlphaStruct.FieldB has no validation
return errs
}
// Validate_BetaStruct validates an instance of BetaStruct according
// to declarative validation rules in the API schema.
func Validate_BetaStruct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *BetaStruct) (errs field.ErrorList) {
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldA", func(obj *BetaStruct) *string { return obj.FieldA }, func(obj *BetaStruct) 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).MarkBeta()...)
return errs
}, []validate.DiscriminatedRule[*string, string]{
{
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).MarkBeta(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return errs
}
return errs
}},
})...)
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "fieldB", func(obj *BetaStruct) *string { return obj.FieldB }, func(obj *BetaStruct) 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).MarkBeta()...)
return errs
}, []validate.DiscriminatedRule[*string, string]{
{
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).MarkBeta(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return errs
}
return errs
}},
})...)
// field BetaStruct.TypeMeta has no validation
// field BetaStruct.D1 has no validation
// field BetaStruct.FieldA has no validation
// field BetaStruct.FieldB has no validation
return errs
}
// Validate_CrossLevels validates an instance of CrossLevels according
// to declarative validation rules in the API schema.
func Validate_CrossLevels(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *CrossLevels) (errs field.ErrorList) {
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "a", func(obj *CrossLevels) *string { return obj.A }, func(obj *CrossLevels) string { return obj.Kind }, 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).MarkAlpha()...)
return errs
}, []validate.DiscriminatedRule[*string, string]{
{
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).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return errs
}
return errs
}},
})...)
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "b", func(obj *CrossLevels) *string { return obj.B }, func(obj *CrossLevels) string { return obj.Kind }, 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).MarkAlpha()...)
return errs
}, []validate.DiscriminatedRule[*string, string]{
{
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).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return errs
}
return errs
}},
})...)
// field CrossLevels.TypeMeta has no validation
// field CrossLevels.Kind has no validation
// field CrossLevels.A has no validation
// field CrossLevels.B has no validation
return errs
}
// Validate_MixedLevels validates an instance of MixedLevels according
// to declarative validation rules in the API schema.
func Validate_MixedLevels(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *MixedLevels) (errs field.ErrorList) {
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "a", func(obj *MixedLevels) *string { return obj.A }, func(obj *MixedLevels) string { return obj.Mode }, 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).MarkAlpha()...)
return errs
}, []validate.DiscriminatedRule[*string, string]{
{
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).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return errs
}
return errs
}},
})...)
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "b", func(obj *MixedLevels) *string { return obj.B }, func(obj *MixedLevels) string { return obj.Mode }, 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).MarkBeta()...)
return errs
}, []validate.DiscriminatedRule[*string, string]{
{
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).MarkBeta(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return errs
}
return errs
}},
})...)
// field MixedLevels.TypeMeta has no validation
// field MixedLevels.Mode has no validation
// field MixedLevels.A has no validation
// field MixedLevels.B has no validation
return errs
}
// Validate_SameFieldMixed validates an instance of SameFieldMixed according
// to declarative validation rules in the API schema.
func Validate_SameFieldMixed(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SameFieldMixed) (errs field.ErrorList) {
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "value", func(obj *SameFieldMixed) *string { return obj.Value }, func(obj *SameFieldMixed) string { return obj.Mode }, 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.DiscriminatedRule[*string, string]{
{
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).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return errs
}
return errs
}},
{
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).MarkBeta(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return errs
}
return errs
}},
})...)
// field SameFieldMixed.TypeMeta has no validation
// field SameFieldMixed.Mode has no validation
// field SameFieldMixed.Value has no validation
return errs
}
// Validate_SameValueMixedPayloads validates an instance of SameValueMixedPayloads according
// to declarative validation rules in the API schema.
func Validate_SameValueMixedPayloads(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *SameValueMixedPayloads) (errs field.ErrorList) {
errs = append(errs, validate.Discriminated(ctx, op, fldPath, obj, oldObj, "value", func(obj *SameValueMixedPayloads) *string { return obj.Value }, func(obj *SameValueMixedPayloads) string { return obj.Mode }, 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.DiscriminatedRule[*string, string]{
{
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).MarkAlpha(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return errs
}
errs = append(errs, validate.MinLength(ctx, op, fldPath, obj, oldObj, 3).MarkBeta()...)
return errs
}},
})...)
// field SameValueMixedPayloads.TypeMeta has no validation
// field SameValueMixedPayloads.Mode has no validation
// field SameValueMixedPayloads.Value has no validation
return errs
}

View File

@@ -63,8 +63,9 @@ type fieldMemberRules struct {
}
type memberRule struct {
value string
validations Validations
value string
validations Validations
stabilityLevel ValidationStabilityLevel
}
func (mg discriminatorGroups) getOrCreate(name string) *discriminatorGroup {
@@ -204,8 +205,9 @@ func (mtv *memberTagValidator) GetValidations(context Context, tag codetags.Tag)
}
group.members[fieldName].rules = append(group.members[fieldName].rules, memberRule{
value: value,
validations: payloadValidations,
value: value,
validations: payloadValidations,
stabilityLevel: context.StabilityLevel,
})
return Validations{}, nil
@@ -323,27 +325,53 @@ func (mtfv *discriminatorFieldValidator) generateMemberFieldValidation(context C
}
// Prepare DiscriminatedRules
// Aggregate rules by value
rulesByValue := make(map[string]Validations)
// Mark each rule's validation functions with their stability level before
// aggregating by value, so that different rules for the same value can
// carry different stability levels.
rulesByValue := make(map[string]*Validations)
var values []string
for _, rule := range rules.rules {
// Track unique discriminator values in order of first appearance.
if _, ok := rulesByValue[rule.value]; !ok {
rulesByValue[rule.value] = &Validations{}
values = append(values, rule.value)
}
v := rulesByValue[rule.value]
v.Add(rule.validations)
rulesByValue[rule.value] = v
// Mark each validation function with its stability level before merging.
v := rule.validations
if rule.stabilityLevel != "" {
marked := make([]FunctionGen, len(v.Functions))
for i, f := range v.Functions {
marked[i] = f.WithStabilityLevel(rule.stabilityLevel)
}
v.Functions = marked
}
// Accumulate this rule's validations with others that share the same discriminator value.
rulesByValue[rule.value].Add(v)
}
slices.Sort(values)
discriminatorType := group.discriminatorMember.Type
// When all rules share the same stability level, the default-forbidden
// (which fires for unrecognized discriminator values) should also be marked
// with that level so its errors carry the same stability annotation.
if uniformLevel := uniformStabilityLevel(rules.rules); uniformLevel != "" {
mwf, ok := defaultForbidden.(MultiWrapperFunction)
if !ok {
return Validations{}, fmt.Errorf("internal error: defaultForbidden is not a MultiWrapperFunction")
}
marked := make([]FunctionGen, len(mwf.Functions))
for i, f := range mwf.Functions {
marked[i] = f.WithStabilityLevel(uniformLevel)
}
mwf.Functions = marked
defaultForbidden = mwf
}
var discriminatedRules []any
for _, val := range values {
ruleValidations := rulesByValue[val]
wrapper := MultiWrapperFunction{
Functions: ruleValidations.Functions,
Functions: rulesByValue[val].Functions,
ObjType: nilableFieldType,
}
@@ -408,6 +436,18 @@ func (mtfv *discriminatorFieldValidator) generateMemberFieldValidation(context C
return Validations{Functions: []FunctionGen{fn}}, nil
}
// uniformStabilityLevel returns the common stability level if all rules share
// the same one, or "" if they differ.
func uniformStabilityLevel(rules []memberRule) ValidationStabilityLevel {
level := rules[0].stabilityLevel
for i := 1; i < len(rules); i++ {
if rules[i].stabilityLevel != level {
return ""
}
}
return level
}
func (mtfv *discriminatorFieldValidator) getForbiddenValidation(t *types.Type) (any, error) {
var forbidden types.Name
nt := util.NativeType(t)