mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-18 13:57:38 +00:00
Add support for validation cohorts
On a given field or type, there may be multiple "cohorts" of validation which need to be processed together. For example, a short-circuit validation and a non-short-circuit for the same subfield. The unnamed cohort (aka the default cohort) is first, followed by named cohorts in order they are created. Named cohorts are emitted as inline functions, so early-return can be used. This allows tags like k8s:subfield or k8s:item to generate cohorts (named after the field's JSON name or the selector string, respectively). Subsequent commits will add support to those tags. Co-Authored-by: Yongrui Lin <yongrlin@outlook.com>
This commit is contained in:
@@ -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"`
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"*cohorts.T": {
|
||||
"": [
|
||||
"type T ShortCircuit"
|
||||
]
|
||||
}
|
||||
}
|
||||
107
staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations.go
generated
Normal file
107
staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/cohorts/zz_generated.validations.go
generated
Normal file
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: "<string>",
|
||||
Docs: "The type arg in generated code (must be the value-type, not pointer).",
|
||||
Type: codetags.ArgTypeString,
|
||||
}, {
|
||||
Name: "cohort",
|
||||
Description: "<string>",
|
||||
Docs: "An optional cohort name to group multiple validations.",
|
||||
Type: codetags.ArgTypeString,
|
||||
}}
|
||||
if frtv.result {
|
||||
doc.Description = "Always passes validation (useful for testing)."
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user