mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-17 20:00:07 +00:00
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:
@@ -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
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user