diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/doc.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/doc.go new file mode 100644 index 00000000000..82482784f49 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/doc.go @@ -0,0 +1,55 @@ +/* +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. +*/ + +// +k8s:validation-gen=TypeMeta +// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme +// +k8s:validation-gen-test-fixture=validateFalse + +// This is a test package. +package cohorts + +import "k8s.io/code-generator/cmd/validation-gen/testscheme" + +var localSchemeBuilder = testscheme.New() + +// Note: the following are out of order on purpose. +// They should be emitted as: +// - T {ShortCircuit,Regular} +// - T c2 {ShortCircuit,Regular} +// - T c1 {ShortCircuit,Regular} +// +// +k8s:validateFalse(cohort: "c2")="type T c2 Regular" +// +k8s:validateFalse(cohort: "c1")="type T c1 Regular" +// +k8s:validateFalse(cohort: "c1", flags: "ShortCircuit")="type T c1 ShortCircuit" +// +k8s:validateFalse(cohort: "c2", flags: "ShortCircuit")="type T c2 ShortCircuit" +// +k8s:validateFalse="type T Regular" +// +k8s:validateFalse(flags: "ShortCircuit")="type T ShortCircuit" +type T struct { + TypeMeta int + + // Note: the following are out of order on purpose. + // They should be emitted as: + // - T.S {ShortCircuit,Regular} + // - T.S c2 {ShortCircuit,Regular} + // - T.S c1 {ShortCircuit,Regular} + // +k8s:validateFalse(cohort: "c2")="field T.S c2 Regular" + // +k8s:validateFalse(cohort: "c1")="field T.S c1 Regular" + // +k8s:validateFalse(cohort: "c1", flags: "ShortCircuit")="field T.S c1 ShortCircuit" + // +k8s:validateFalse(cohort: "c2", flags: "ShortCircuit")="field T.S c2 ShortCircuit" + // +k8s:validateFalse="field T.S Regular" + // +k8s:validateFalse(flags: "ShortCircuit")="field T.S ShortCircuit" + S string `json:"s"` +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/testdata/validate-false.json b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/testdata/validate-false.json new file mode 100644 index 00000000000..ad04e399125 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/testdata/validate-false.json @@ -0,0 +1,7 @@ +{ + "*cohorts.T": { + "": [ + "type T ShortCircuit" + ] + } +} \ No newline at end of file diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations.go new file mode 100644 index 00000000000..04c5b27a016 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations.go @@ -0,0 +1,107 @@ +//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 cohorts + +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 T + scheme.AddValidationFunc((*T)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList { + switch op.Request.SubresourcePath() { + case "/": + return Validate_T(ctx, op, nil /* fldPath */, obj.(*T), safe.Cast[*T](oldObj)) + } + return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))} + }) + return nil +} + +// Validate_T validates an instance of T according +// to declarative validation rules in the API schema. +func Validate_T(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *T) (errs field.ErrorList) { + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T ShortCircuit"); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T Regular")...) + func() { // cohort c2 + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T c2 ShortCircuit"); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T c2 Regular")...) + }() + func() { // cohort c1 + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T c1 ShortCircuit"); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "type T c1 Regular")...) + }() + + // field T.TypeMeta has no validation + + // field T.S + errs = append(errs, + func(fldPath *field.Path, obj, oldObj *string) (errs field.ErrorList) { + // don't revalidate unchanged data + if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) { + return nil + } + // call field-attached validations + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T.S ShortCircuit"); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T.S Regular")...) + func() { // cohort c2 + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T.S c2 ShortCircuit"); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T.S c2 Regular")...) + }() + func() { // cohort c1 + if e := validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T.S c1 ShortCircuit"); len(e) != 0 { + errs = append(errs, e...) + return // do not proceed + } + errs = append(errs, validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "field T.S c1 Regular")...) + }() + return + }(fldPath.Child("s"), &obj.S, safe.Field(oldObj, func(oldObj *T) *string { return &oldObj.S }))...) + + return errs +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations_test.go new file mode 100644 index 00000000000..e9214fd3eb3 --- /dev/null +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations_test.go @@ -0,0 +1,30 @@ +//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 cohorts + +import ( + "testing" +) + +func TestValidation(t *testing.T) { + localSchemeBuilder.Test(t).ValidateFixtures() +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validation.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validation.go index b327438bb53..2d47bf2c2eb 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validation.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validation.go @@ -1280,12 +1280,138 @@ func emitRatchetingCheck(c *generator.Context, t *types.Type, sw *generator.Snip // variables named "obj" and "oldObj", and the field path to this value is // named "fldPath". func emitCallsToValidators(c *generator.Context, validations []validators.FunctionGen, sw *generator.SnippetWriter) { - // Helper func - sort := func(in []validators.FunctionGen) []validators.FunctionGen { - sooner := make([]validators.FunctionGen, 0, len(in)) - later := make([]validators.FunctionGen, 0, len(in)) + // Group and sort the inputs. + cohorts := sortIntoCohorts(validations) - for _, fg := range in { + for _, validations := range cohorts { + cohortName := validations[0].Cohort + if cohortName != "" { + sw.Do("func() { // cohort $.$\n", cohortName) + } + for _, v := range validations { + isShortCircuit := v.Flags.IsSet(validators.ShortCircuit) + isNonError := v.Flags.IsSet(validators.NonError) + + targs := generator.Args{ + "funcName": c.Universe.Type(v.Function), + "field": mkSymbolArgs(c, fieldPkgSymbols), + } + + emitCall := func() { + sw.Do("$.funcName|raw$", targs) + if typeArgs := v.TypeArgs; len(typeArgs) > 0 { + sw.Do("[", nil) + for i, typeArg := range typeArgs { + sw.Do("$.|raw$", c.Universe.Type(typeArg)) + if i < len(typeArgs)-1 { + sw.Do(",", nil) + } + } + sw.Do("]", nil) + } + sw.Do("(ctx, op, fldPath, obj, oldObj", targs) + for _, arg := range v.Args { + sw.Do(", ", nil) + toGolangSourceDataLiteral(sw, c, arg) + } + sw.Do(")", targs) + } + + // If validation is conditional, wrap the validation function with a conditions check. + if !v.Conditions.Empty() { + emitBaseFunction := emitCall + emitCall = func() { + sw.Do("func() $.field.ErrorList|raw$ {\n", targs) + sw.Do(" if ", nil) + firstCondition := true + if len(v.Conditions.OptionEnabled) > 0 { + sw.Do("op.HasOption($.$)", strconv.Quote(v.Conditions.OptionEnabled)) + firstCondition = false + } + if len(v.Conditions.OptionDisabled) > 0 { + if !firstCondition { + sw.Do(" && ", nil) + } + sw.Do("!op.HasOption($.$)", strconv.Quote(v.Conditions.OptionDisabled)) + } + sw.Do(" {\n", nil) + sw.Do(" return ", nil) + emitBaseFunction() + sw.Do("\n", nil) + sw.Do(" } else {\n", nil) + sw.Do(" return nil // skip validation\n", nil) + sw.Do(" }\n", nil) + sw.Do("}()", nil) + } + } + + for _, comment := range v.Comments { + sw.Do("// $.$\n", comment) + } + if isShortCircuit { + sw.Do("if e := ", nil) + emitCall() + sw.Do("; len(e) != 0 {\n", nil) + if !isNonError { + sw.Do("errs = append(errs, e...)\n", nil) + } + sw.Do(" return // do not proceed\n", nil) + sw.Do("}\n", nil) + } else { + if isNonError { + emitCall() + } else { + sw.Do("errs = append(errs, ", nil) + emitCall() + sw.Do("...)\n", nil) + } + } + } + if cohortName != "" { + sw.Do("}()\n", nil) + } + } +} + +// sortIntoCohorts groups the inputs into a list of cohorts. Within each +// cohort, function calls are sorted such that short-circuiting function +// calls are handled before others. The first cohort is always the +// default cohort (named "") if it exists. Other cohorts are returned in +// the order they were defined in the input. +func sortIntoCohorts(in []validators.FunctionGen) [][]validators.FunctionGen { + defaultCohort := make([]validators.FunctionGen, 0, len(in)) + namedCohorts := map[string][]validators.FunctionGen{} + idx := make([]string, 0, len(in)) + for _, fg := range in { + key := fg.Cohort + if key == "" { + defaultCohort = append(defaultCohort, fg) + } else { + if !slices.Contains(idx, key) { + idx = append(idx, key) + } + namedCohorts[key] = append(namedCohorts[key], fg) + } + } + if len(defaultCohort) > 0 { + idx = append([]string{""}, idx...) + } + // NOTE: we do not sort cohorts by name, because we want to preserve + // their definition order. + + result := make([][]validators.FunctionGen, 0, len(in)) + for _, key := range idx { + var cohort []validators.FunctionGen + if key == "" { + cohort = defaultCohort + } else { + cohort = namedCohorts[key] + } + + sooner := make([]validators.FunctionGen, 0, len(cohort)) + later := make([]validators.FunctionGen, 0, len(cohort)) + + for _, fg := range cohort { isShortCircuit := (fg.Flags.IsSet(validators.ShortCircuit)) if isShortCircuit { @@ -1294,92 +1420,11 @@ func emitCallsToValidators(c *generator.Context, validations []validators.Functi later = append(later, fg) } } - result := sooner - result = append(result, later...) - return result - } - - validations = sort(validations) - - for _, v := range validations { - isShortCircuit := v.Flags.IsSet(validators.ShortCircuit) - isNonError := v.Flags.IsSet(validators.NonError) - - targs := generator.Args{ - "funcName": c.Universe.Type(v.Function), - "field": mkSymbolArgs(c, fieldPkgSymbols), - } - - emitCall := func() { - sw.Do("$.funcName|raw$", targs) - if typeArgs := v.TypeArgs; len(typeArgs) > 0 { - sw.Do("[", nil) - for i, typeArg := range typeArgs { - sw.Do("$.|raw$", c.Universe.Type(typeArg)) - if i < len(typeArgs)-1 { - sw.Do(",", nil) - } - } - sw.Do("]", nil) - } - sw.Do("(ctx, op, fldPath, obj, oldObj", targs) - for _, arg := range v.Args { - sw.Do(", ", nil) - toGolangSourceDataLiteral(sw, c, arg) - } - sw.Do(")", targs) - } - - // If validation is conditional, wrap the validation function with a conditions check. - if !v.Conditions.Empty() { - emitBaseFunction := emitCall - emitCall = func() { - sw.Do("func() $.field.ErrorList|raw$ {\n", targs) - sw.Do(" if ", nil) - firstCondition := true - if len(v.Conditions.OptionEnabled) > 0 { - sw.Do("op.HasOption($.$)", strconv.Quote(v.Conditions.OptionEnabled)) - firstCondition = false - } - if len(v.Conditions.OptionDisabled) > 0 { - if !firstCondition { - sw.Do(" && ", nil) - } - sw.Do("!op.HasOption($.$)", strconv.Quote(v.Conditions.OptionDisabled)) - } - sw.Do(" {\n", nil) - sw.Do(" return ", nil) - emitBaseFunction() - sw.Do("\n", nil) - sw.Do(" } else {\n", nil) - sw.Do(" return nil // skip validation\n", nil) - sw.Do(" }\n", nil) - sw.Do("}()", nil) - } - } - - for _, comment := range v.Comments { - sw.Do("// $.$\n", comment) - } - if isShortCircuit { - sw.Do("if e := ", nil) - emitCall() - sw.Do("; len(e) != 0 {\n", nil) - if !isNonError { - sw.Do("errs = append(errs, e...)\n", nil) - } - sw.Do(" return // do not proceed\n", nil) - sw.Do("}\n", nil) - } else { - if isNonError { - emitCall() - } else { - sw.Do("errs = append(errs, ", nil) - emitCall() - sw.Do("...)\n", nil) - } - } + sorted := sooner + sorted = append(sorted, later...) + result = append(result, sorted) } + return result } func emitComments(comments []string, sw *generator.SnippetWriter) { diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validation_test.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validation_test.go index 5debd0856e8..5d53a9309a9 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validation_test.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validation_test.go @@ -17,8 +17,10 @@ limitations under the License. package main import ( + "reflect" "testing" + "k8s.io/code-generator/cmd/validation-gen/validators" "k8s.io/gengo/v2/types" ) @@ -366,3 +368,111 @@ func TestGetLeafTypeAndPrefixes(t *testing.T) { } } } + +func TestSortIntoCohorts(t *testing.T) { + cases := []struct { + in []validators.FunctionGen + expected [][]validators.FunctionGen + }{{ + // empty + in: []validators.FunctionGen{}, + expected: [][]validators.FunctionGen{}, + }, { + // default cohort + in: []validators.FunctionGen{ + {TagName: "a", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "b", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "c", Cohort: "", Flags: validators.DefaultFlags}, + }, + expected: [][]validators.FunctionGen{{ + {TagName: "a", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "b", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "c", Cohort: "", Flags: validators.DefaultFlags}, + }}, + }, { + // default cohort, not already sorted by name + in: []validators.FunctionGen{ + {TagName: "c", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "b", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "a", Cohort: "", Flags: validators.DefaultFlags}, + }, + expected: [][]validators.FunctionGen{{ + {TagName: "c", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "b", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "a", Cohort: "", Flags: validators.DefaultFlags}, + }}, + }, { + // default cohort, with a short-circuit + in: []validators.FunctionGen{ + {TagName: "a", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "b", Cohort: "", Flags: validators.ShortCircuit}, + {TagName: "c", Cohort: "", Flags: validators.DefaultFlags}, + }, + expected: [][]validators.FunctionGen{{ + {TagName: "b", Cohort: "", Flags: validators.ShortCircuit}, + {TagName: "a", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "c", Cohort: "", Flags: validators.DefaultFlags}, + }}, + }, { + // default cohort, with 2 short-circuits + in: []validators.FunctionGen{ + {TagName: "a", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "b", Cohort: "", Flags: validators.ShortCircuit}, + {TagName: "c", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "d", Cohort: "", Flags: validators.ShortCircuit}, + }, + expected: [][]validators.FunctionGen{{ + {TagName: "b", Cohort: "", Flags: validators.ShortCircuit}, + {TagName: "d", Cohort: "", Flags: validators.ShortCircuit}, + {TagName: "a", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "c", Cohort: "", Flags: validators.DefaultFlags}, + }}, + }, { + // default and non-default cohorts + in: []validators.FunctionGen{ + {TagName: "a", Cohort: "foo", Flags: validators.DefaultFlags}, + {TagName: "b", Cohort: "bar", Flags: validators.DefaultFlags}, + {TagName: "c", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "d", Cohort: "foo", Flags: validators.DefaultFlags}, + {TagName: "e", Cohort: "bar", Flags: validators.DefaultFlags}, + {TagName: "f", Cohort: "", Flags: validators.DefaultFlags}, + }, + expected: [][]validators.FunctionGen{{ + {TagName: "c", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "f", Cohort: "", Flags: validators.DefaultFlags}, + }, { + {TagName: "a", Cohort: "foo", Flags: validators.DefaultFlags}, + {TagName: "d", Cohort: "foo", Flags: validators.DefaultFlags}, + }, { + {TagName: "b", Cohort: "bar", Flags: validators.DefaultFlags}, + {TagName: "e", Cohort: "bar", Flags: validators.DefaultFlags}, + }}, + }, { + // default and non-default cohorts with short-circuit + in: []validators.FunctionGen{ + {TagName: "a", Cohort: "foo", Flags: validators.DefaultFlags}, + {TagName: "b", Cohort: "bar", Flags: validators.DefaultFlags}, + {TagName: "c", Cohort: "", Flags: validators.DefaultFlags}, + {TagName: "d", Cohort: "foo", Flags: validators.ShortCircuit}, + {TagName: "e", Cohort: "bar", Flags: validators.ShortCircuit}, + {TagName: "f", Cohort: "", Flags: validators.ShortCircuit}, + }, + expected: [][]validators.FunctionGen{{ + {TagName: "f", Cohort: "", Flags: validators.ShortCircuit}, + {TagName: "c", Cohort: "", Flags: validators.DefaultFlags}, + }, { + {TagName: "d", Cohort: "foo", Flags: validators.ShortCircuit}, + {TagName: "a", Cohort: "foo", Flags: validators.DefaultFlags}, + }, { + {TagName: "e", Cohort: "bar", Flags: validators.ShortCircuit}, + {TagName: "b", Cohort: "bar", Flags: validators.DefaultFlags}, + }}, + }} + + for _, tc := range cases { + out := sortIntoCohorts(tc.in) + if !reflect.DeepEqual(out, tc.expected) { + t.Errorf("expected %v, got %v", tc.expected, out) + } + } +} diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/testing.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/testing.go index 99679abb7d8..fc9dafd705a 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/testing.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/testing.go @@ -73,7 +73,9 @@ func (frtv fixedResultTagValidator) GetValidations(context Context, tag codetags if err != nil { return result, fmt.Errorf("can't decode tag payload: %w", err) } - result.AddFunction(Function(frtv.TagName(), args.flags, fixedResultValidator, frtv.result, args.msg).WithTypeArgs(args.typeArgs...)) + fn := Function(frtv.TagName(), args.flags, fixedResultValidator, frtv.result, args.msg).WithTypeArgs(args.typeArgs...) + fn.Cohort = args.cohort + result.AddFunction(fn) return result, nil } @@ -86,6 +88,7 @@ type fixedResultArgs struct { flags FunctionFlags msg string typeArgs []types.Name + cohort string } func (fixedResultTagValidator) toFixedResultArgs(in codetags.Tag) (fixedResultArgs, error) { @@ -111,6 +114,8 @@ func (fixedResultTagValidator) toFixedResultArgs(in codetags.Tag) (fixedResultAr } result.typeArgs = []types.Name{{Package: "", Name: tn}} } + case "cohort": + result.cohort = a.Value } } if in.ValueType == codetags.ValueTypeString { @@ -149,6 +154,11 @@ func (frtv fixedResultTagValidator) Docs() TagDoc { Description: "", Docs: "The type arg in generated code (must be the value-type, not pointer).", Type: codetags.ArgTypeString, + }, { + Name: "cohort", + Description: "", + Docs: "An optional cohort name to group multiple validations.", + Type: codetags.ArgTypeString, }} if frtv.result { doc.Description = "Always passes validation (useful for testing)." diff --git a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/validators.go b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/validators.go index 40e3adc3cb4..a9f488259cf 100644 --- a/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/validators.go +++ b/staging/src/k8s.io/code-generator/cmd/validation-gen/validators/validators.go @@ -445,6 +445,10 @@ type FunctionGen struct { // TagName is the tag which triggered this function. TagName string + // Cohort indicates a set of related functions which are processed + // together. + Cohort string + // Flags holds the options for this validator function. Flags FunctionFlags