mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
Add validators: optional/required/forbidden
Co-authored-by: Tim Hockin <thockin@google.com> Co-authored-by: Aaron Prindle <aprindle@google.com> Co-authored-by: Yongrui Lin <yongrlin@google.com>
This commit is contained in:
parent
31f4637217
commit
63050550c3
133
staging/src/k8s.io/apimachinery/pkg/api/validate/required.go
Normal file
133
staging/src/k8s.io/apimachinery/pkg/api/validate/required.go
Normal file
@ -0,0 +1,133 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// RequiredValue verifies that the specified value is not the zero-value for
|
||||
// its type.
|
||||
func RequiredValue[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
var zero T
|
||||
if *value != zero {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{field.Required(fldPath, "")}
|
||||
}
|
||||
|
||||
// RequiredPointer verifies that the specified pointer is not nil.
|
||||
func RequiredPointer[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value != nil {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{field.Required(fldPath, "")}
|
||||
}
|
||||
|
||||
// RequiredSlice verifies that the specified slice is not empty.
|
||||
func RequiredSlice[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList {
|
||||
if len(value) > 0 {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{field.Required(fldPath, "")}
|
||||
}
|
||||
|
||||
// RequiredMap verifies that the specified map is not empty.
|
||||
func RequiredMap[K comparable, T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]T) field.ErrorList {
|
||||
if len(value) > 0 {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{field.Required(fldPath, "")}
|
||||
}
|
||||
|
||||
// ForbiddenValue verifies that the specified value is the zero-value for its
|
||||
// type.
|
||||
func ForbiddenValue[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
var zero T
|
||||
if *value == zero {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{field.Forbidden(fldPath, "")}
|
||||
}
|
||||
|
||||
// ForbiddenPointer verifies that the specified pointer is nil.
|
||||
func ForbiddenPointer[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{field.Forbidden(fldPath, "")}
|
||||
}
|
||||
|
||||
// ForbiddenSlice verifies that the specified slice is empty.
|
||||
func ForbiddenSlice[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList {
|
||||
if len(value) == 0 {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{field.Forbidden(fldPath, "")}
|
||||
}
|
||||
|
||||
// RequiredMap verifies that the specified map is empty.
|
||||
func ForbiddenMap[K comparable, T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]T) field.ErrorList {
|
||||
if len(value) == 0 {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{field.Forbidden(fldPath, "")}
|
||||
}
|
||||
|
||||
// OptionalValue verifies that the specified value is not the zero-value for
|
||||
// its type. This is identical to RequiredValue, but the caller should treat an
|
||||
// error here as an indication that the optional value was not specified.
|
||||
func OptionalValue[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
var zero T
|
||||
if *value != zero {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{field.Required(fldPath, "optional value was not specified")}
|
||||
}
|
||||
|
||||
// OptionalPointer verifies that the specified pointer is not nil. This is
|
||||
// identical to RequiredPointer, but the caller should treat an error here as an
|
||||
// indication that the optional value was not specified.
|
||||
func OptionalPointer[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value != nil {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{field.Required(fldPath, "optional value was not specified")}
|
||||
}
|
||||
|
||||
// OptionalSlice verifies that the specified slice is not empty. This is
|
||||
// identical to RequiredSlice, but the caller should treat an error here as an
|
||||
// indication that the optional value was not specified.
|
||||
func OptionalSlice[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList {
|
||||
if len(value) > 0 {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{field.Required(fldPath, "optional value was not specified")}
|
||||
}
|
||||
|
||||
// OptionalMap verifies that the specified map is not empty. This is identical
|
||||
// to RequiredMap, but the caller should treat an error here as an indication that
|
||||
// the optional value was not specified.
|
||||
func OptionalMap[K comparable, T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ map[K]T) field.ErrorList {
|
||||
if len(value) > 0 {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{field.Required(fldPath, "optional value was not specified")}
|
||||
}
|
@ -0,0 +1,924 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestRequiredValue(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := "value"
|
||||
return RequiredValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := "" // zero-value
|
||||
return RequiredValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := 123
|
||||
return RequiredValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := 0 // zero-value
|
||||
return RequiredValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := true
|
||||
return RequiredValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := false // zero-value
|
||||
return RequiredValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := struct{ S string }{"value"}
|
||||
return RequiredValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := struct{ S string }{} // zero-value
|
||||
return RequiredValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := ptr.To("")
|
||||
return RequiredValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := (*string)(nil) // zero-value
|
||||
return RequiredValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredPointer(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := ""
|
||||
return RequiredPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (*string)(nil)
|
||||
return RequiredPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := 0
|
||||
return RequiredPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (*int)(nil)
|
||||
return RequiredPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := false
|
||||
return RequiredPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (*bool)(nil)
|
||||
return RequiredPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := struct{ S string }{}
|
||||
return RequiredPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (*struct{ S string })(nil)
|
||||
return RequiredPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := (*string)(nil)
|
||||
return RequiredPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (**string)(nil)
|
||||
return RequiredPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredSlice(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []string{""}
|
||||
return RequiredSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []string{}
|
||||
return RequiredSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []int{0}
|
||||
return RequiredSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []int{}
|
||||
return RequiredSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []bool{false}
|
||||
return RequiredSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []bool{}
|
||||
return RequiredSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []*string{nil}
|
||||
return RequiredSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []*string{}
|
||||
return RequiredSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRequiredMap(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[string]string{"": ""}
|
||||
return RequiredMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[string]string{}
|
||||
return RequiredMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[int]int{0: 0}
|
||||
return RequiredMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[int]int{}
|
||||
return RequiredMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[bool]bool{false: false}
|
||||
return RequiredMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[string]bool{}
|
||||
return RequiredMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Required value",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionalValue(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := "value"
|
||||
return OptionalValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := "" // zero-value
|
||||
return OptionalValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := 123
|
||||
return OptionalValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := 0 // zero-value
|
||||
return OptionalValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := true
|
||||
return OptionalValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := false // zero-value
|
||||
return OptionalValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := struct{ S string }{"value"}
|
||||
return OptionalValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := struct{ S string }{} // zero-value
|
||||
return OptionalValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := ptr.To("")
|
||||
return OptionalValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := (*string)(nil) // zero-value
|
||||
return OptionalValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionalPointer(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := ""
|
||||
return OptionalPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (*string)(nil)
|
||||
return OptionalPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := 0
|
||||
return OptionalPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (*int)(nil)
|
||||
return OptionalPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := false
|
||||
return OptionalPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (*bool)(nil)
|
||||
return OptionalPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := struct{ S string }{}
|
||||
return OptionalPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (*struct{ S string })(nil)
|
||||
return OptionalPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := (*string)(nil)
|
||||
return OptionalPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (**string)(nil)
|
||||
return OptionalPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionalSlice(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []string{""}
|
||||
return OptionalSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []string{}
|
||||
return OptionalSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []int{0}
|
||||
return OptionalSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []int{}
|
||||
return OptionalSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []bool{false}
|
||||
return OptionalSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []bool{}
|
||||
return OptionalSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []*string{nil}
|
||||
return OptionalSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []*string{}
|
||||
return OptionalSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionalMap(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[string]string{"": ""}
|
||||
return OptionalMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[string]string{}
|
||||
return OptionalMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[int]int{0: 0}
|
||||
return OptionalMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[int]int{}
|
||||
return OptionalMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[bool]bool{false: false}
|
||||
return OptionalMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[string]bool{}
|
||||
return OptionalMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath:.*optional value was not specified",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestForbiddenValue(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := ""
|
||||
return ForbiddenValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := "value"
|
||||
return ForbiddenValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := 0
|
||||
return ForbiddenValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := 123
|
||||
return ForbiddenValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := false
|
||||
return ForbiddenValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := true
|
||||
return ForbiddenValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := struct{ S string }{}
|
||||
return ForbiddenValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := struct{ S string }{"value"}
|
||||
return ForbiddenValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := (*string)(nil)
|
||||
return ForbiddenValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := ptr.To("")
|
||||
return ForbiddenValue(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestForbiddenPointer(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (*string)(nil)
|
||||
return ForbiddenPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := ""
|
||||
return ForbiddenPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (*int)(nil)
|
||||
return ForbiddenPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := 0
|
||||
return ForbiddenPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (*bool)(nil)
|
||||
return ForbiddenPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := false
|
||||
return ForbiddenPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (*struct{ S string })(nil)
|
||||
return ForbiddenPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := struct{ S string }{}
|
||||
return ForbiddenPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
pointer := (**string)(nil)
|
||||
return ForbiddenPointer(context.Background(), op, fp, pointer, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := (*string)(nil)
|
||||
return ForbiddenPointer(context.Background(), op, fp, &value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestForbiddenSlice(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []string{}
|
||||
return ForbiddenSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []string{""}
|
||||
return ForbiddenSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []int{}
|
||||
return ForbiddenSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []int{0}
|
||||
return ForbiddenSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []bool{}
|
||||
return ForbiddenSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []bool{false}
|
||||
return ForbiddenSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []*string{}
|
||||
return ForbiddenSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := []*string{nil}
|
||||
return ForbiddenSlice(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestForbiddenMap(t *testing.T) {
|
||||
cases := []struct {
|
||||
fn func(op operation.Operation, fp *field.Path) field.ErrorList
|
||||
err string // regex
|
||||
}{{
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[string]string{}
|
||||
return ForbiddenMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[string]string{"": ""}
|
||||
return ForbiddenMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[int]int{}
|
||||
return ForbiddenMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[int]int{0: 0}
|
||||
return ForbiddenMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[string]bool{}
|
||||
return ForbiddenMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
}, {
|
||||
fn: func(op operation.Operation, fp *field.Path) field.ErrorList {
|
||||
value := map[bool]bool{false: false}
|
||||
return ForbiddenMap(context.Background(), op, fp, value, nil)
|
||||
},
|
||||
err: "fldpath: Forbidden",
|
||||
}}
|
||||
|
||||
for i, tc := range cases {
|
||||
result := tc.fn(operation.Operation{}, field.NewPath("fldpath"))
|
||||
if len(result) > 0 && tc.err == "" {
|
||||
t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if len(result) == 0 && tc.err != "" {
|
||||
t.Errorf("case %d: unexpected success: expected %q", i, tc.err)
|
||||
continue
|
||||
}
|
||||
if len(result) > 0 {
|
||||
if len(result) > 1 {
|
||||
t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result))
|
||||
continue
|
||||
}
|
||||
if re := regexp.MustCompile(tc.err); !re.MatchString(result[0].Error()) {
|
||||
t.Errorf("case %d: wrong error\nexpected: %q\n got: %v", i, tc.err, fmtErrs(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// fmtErrs is a helper for nicer test output. It will use multiple lines if
|
||||
// errs has more than 1 item.
|
||||
func fmtErrs(errs field.ErrorList) string {
|
||||
if len(errs) == 0 {
|
||||
return "<no errors>"
|
||||
}
|
||||
if len(errs) == 1 {
|
||||
return strconv.Quote(errs[0].Error())
|
||||
}
|
||||
buf := bytes.Buffer{}
|
||||
for _, e := range errs {
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString(strconv.Quote(e.Error()))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
/*
|
||||
Copyright 2024 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 validators
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/gengo/v2/types"
|
||||
)
|
||||
|
||||
const (
|
||||
requiredTagName = "k8s:required"
|
||||
optionalTagName = "k8s:optional"
|
||||
forbiddenTagName = "k8s:forbidden"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterTagValidator(requirednessTagValidator{requirednessRequired})
|
||||
RegisterTagValidator(requirednessTagValidator{requirednessOptional})
|
||||
RegisterTagValidator(requirednessTagValidator{requirednessForbidden})
|
||||
}
|
||||
|
||||
// requirednessTagValidator implements multiple modes of requiredness.
|
||||
type requirednessTagValidator struct {
|
||||
mode requirednessMode
|
||||
}
|
||||
|
||||
type requirednessMode string
|
||||
|
||||
const (
|
||||
requirednessRequired requirednessMode = requiredTagName
|
||||
requirednessOptional requirednessMode = optionalTagName
|
||||
requirednessForbidden requirednessMode = forbiddenTagName
|
||||
)
|
||||
|
||||
func (requirednessTagValidator) Init(_ Config) {}
|
||||
|
||||
func (rtv requirednessTagValidator) TagName() string {
|
||||
return string(rtv.mode)
|
||||
}
|
||||
|
||||
var requirednessTagValidScopes = sets.New(ScopeField)
|
||||
|
||||
func (requirednessTagValidator) ValidScopes() sets.Set[Scope] {
|
||||
return requirednessTagValidScopes
|
||||
}
|
||||
|
||||
func (rtv requirednessTagValidator) GetValidations(context Context, _ []string, _ string) (Validations, error) {
|
||||
if context.Type.Kind == types.Alias {
|
||||
panic("alias type should already have been unwrapped")
|
||||
}
|
||||
switch rtv.mode {
|
||||
case requirednessRequired:
|
||||
return rtv.doRequired(context)
|
||||
case requirednessOptional:
|
||||
return rtv.doOptional(context)
|
||||
case requirednessForbidden:
|
||||
return rtv.doForbidden(context)
|
||||
}
|
||||
panic(fmt.Sprintf("unknown requiredness mode: %q", rtv.mode))
|
||||
}
|
||||
|
||||
var (
|
||||
requiredValueValidator = types.Name{Package: libValidationPkg, Name: "RequiredValue"}
|
||||
requiredPointerValidator = types.Name{Package: libValidationPkg, Name: "RequiredPointer"}
|
||||
requiredSliceValidator = types.Name{Package: libValidationPkg, Name: "RequiredSlice"}
|
||||
requiredMapValidator = types.Name{Package: libValidationPkg, Name: "RequiredMap"}
|
||||
)
|
||||
|
||||
// TODO: It might be valuable to have a string payload for when requiredness is
|
||||
// conditional (e.g. required when <otherfield> is specified).
|
||||
func (requirednessTagValidator) doRequired(context Context) (Validations, error) {
|
||||
// Most validators don't care whether the value they are validating was
|
||||
// originally defined as a value-type or a pointer-type in the API. This
|
||||
// one does. Since Go doesn't do partial specialization of templates, we
|
||||
// do manual dispatch here.
|
||||
switch context.Type.Kind {
|
||||
case types.Slice:
|
||||
return Validations{Functions: []FunctionGen{Function(requiredTagName, ShortCircuit, requiredSliceValidator)}}, nil
|
||||
case types.Map:
|
||||
return Validations{Functions: []FunctionGen{Function(requiredTagName, ShortCircuit, requiredMapValidator)}}, nil
|
||||
case types.Pointer:
|
||||
return Validations{Functions: []FunctionGen{Function(requiredTagName, ShortCircuit, requiredPointerValidator)}}, nil
|
||||
case types.Struct:
|
||||
// The +required tag on a non-pointer struct is only for documentation.
|
||||
// We don't perform validation here and defer the validation to
|
||||
// the struct's fields.
|
||||
return Validations{Comments: []string{"required non-pointer structs are purely documentation"}}, nil
|
||||
}
|
||||
return Validations{Functions: []FunctionGen{Function(requiredTagName, ShortCircuit, requiredValueValidator)}}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
optionalValueValidator = types.Name{Package: libValidationPkg, Name: "OptionalValue"}
|
||||
optionalPointerValidator = types.Name{Package: libValidationPkg, Name: "OptionalPointer"}
|
||||
optionalSliceValidator = types.Name{Package: libValidationPkg, Name: "OptionalSlice"}
|
||||
optionalMapValidator = types.Name{Package: libValidationPkg, Name: "OptionalMap"}
|
||||
)
|
||||
|
||||
func (requirednessTagValidator) doOptional(context Context) (Validations, error) {
|
||||
// Most validators don't care whether the value they are validating was
|
||||
// originally defined as a value-type or a pointer-type in the API. This
|
||||
// one does. Since Go doesn't do partial specialization of templates, we
|
||||
// do manual dispatch here.
|
||||
switch context.Type.Kind {
|
||||
case types.Slice:
|
||||
return Validations{Functions: []FunctionGen{Function(optionalTagName, ShortCircuit|NonError, optionalSliceValidator)}}, nil
|
||||
case types.Map:
|
||||
return Validations{Functions: []FunctionGen{Function(optionalTagName, ShortCircuit|NonError, optionalMapValidator)}}, nil
|
||||
case types.Pointer:
|
||||
return Validations{Functions: []FunctionGen{Function(optionalTagName, ShortCircuit|NonError, optionalPointerValidator)}}, nil
|
||||
case types.Struct:
|
||||
// Specifying that a non-pointer struct is optional doesn't actually
|
||||
// make sense technically almost ever, and is better described as a
|
||||
// union inside the struct. It does, however, make sense as
|
||||
// documentation.
|
||||
return Validations{Comments: []string{"optional non-pointer structs are purely documentation"}}, nil
|
||||
}
|
||||
return Validations{Functions: []FunctionGen{Function(optionalTagName, ShortCircuit|NonError, optionalValueValidator)}}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
forbiddenValueValidator = types.Name{Package: libValidationPkg, Name: "ForbiddenValue"}
|
||||
forbiddenPointerValidator = types.Name{Package: libValidationPkg, Name: "ForbiddenPointer"}
|
||||
forbiddenSliceValidator = types.Name{Package: libValidationPkg, Name: "ForbiddenSlice"}
|
||||
forbiddenMapValidator = types.Name{Package: libValidationPkg, Name: "ForbiddenMap"}
|
||||
)
|
||||
|
||||
// TODO: It might be valuable to have a string payload for when forbidden is
|
||||
// conditional (e.g. forbidden when <option> is disabled).
|
||||
func (requirednessTagValidator) doForbidden(context Context) (Validations, error) {
|
||||
// Forbidden is weird. Each of these emits two checks, which are polar
|
||||
// opposites. If the field fails the forbidden check, it will
|
||||
// short-circuit and not run the optional check. If it passes the
|
||||
// forbidden check, it must not be specified, so it will "fail" the
|
||||
// optional check and short-circuit (but without error). Why? For
|
||||
// example, this prevents any further validation from trying to run on a
|
||||
// nil pointer.
|
||||
switch context.Type.Kind {
|
||||
case types.Slice:
|
||||
return Validations{
|
||||
Functions: []FunctionGen{
|
||||
Function(forbiddenTagName, ShortCircuit, forbiddenSliceValidator),
|
||||
Function(forbiddenTagName, ShortCircuit|NonError, optionalSliceValidator),
|
||||
},
|
||||
}, nil
|
||||
case types.Map:
|
||||
return Validations{
|
||||
Functions: []FunctionGen{
|
||||
Function(forbiddenTagName, ShortCircuit, forbiddenMapValidator),
|
||||
Function(forbiddenTagName, ShortCircuit|NonError, optionalMapValidator),
|
||||
},
|
||||
}, nil
|
||||
case types.Pointer:
|
||||
return Validations{
|
||||
Functions: []FunctionGen{
|
||||
Function(forbiddenTagName, ShortCircuit, forbiddenPointerValidator),
|
||||
Function(forbiddenTagName, ShortCircuit|NonError, optionalPointerValidator),
|
||||
},
|
||||
}, nil
|
||||
case types.Struct:
|
||||
// The +forbidden tag on a non-pointer struct is not supported.
|
||||
// If you encounter this error and believe you have a valid use case
|
||||
// for forbiddening a non-pointer struct, please let us know! We need
|
||||
// to understand your scenario to determine if we need to adjust
|
||||
// this behavior or provide alternative validation mechanisms.
|
||||
return Validations{}, fmt.Errorf("non-pointer structs cannot use the %q tag", forbiddenTagName)
|
||||
}
|
||||
return Validations{
|
||||
Functions: []FunctionGen{
|
||||
Function(forbiddenTagName, ShortCircuit, forbiddenValueValidator),
|
||||
Function(forbiddenTagName, ShortCircuit|NonError, optionalValueValidator),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (rtv requirednessTagValidator) Docs() TagDoc {
|
||||
doc := TagDoc{
|
||||
Tag: rtv.TagName(),
|
||||
Scopes: rtv.ValidScopes().UnsortedList(),
|
||||
}
|
||||
|
||||
switch rtv.mode {
|
||||
case requirednessRequired:
|
||||
doc.Description = "Indicates that a field must be specified by clients."
|
||||
case requirednessOptional:
|
||||
doc.Description = "Indicates that a field is optional to clients."
|
||||
case requirednessForbidden:
|
||||
doc.Description = "Indicates that a field may not be specified."
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown requiredness mode: %q", rtv.mode))
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
Loading…
Reference in New Issue
Block a user