Skip to content

Commit 31f4637

Browse files
jpbetzthockinaaron-prindleyongruilin
committed
Add validators: eachkey, eachval, subfield
Introduce a composable set of tags for validating child data. This allows for point-of-use validation of shared types. Co-authored-by: Tim Hockin <[email protected]> Co-authored-by: Aaron Prindle <[email protected]> Co-authored-by: Yongrui Lin <[email protected]>
1 parent b5f9a00 commit 31f4637

File tree

5 files changed

+1032
-0
lines changed

5 files changed

+1032
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package validate
18+
19+
import (
20+
"context"
21+
22+
"k8s.io/apimachinery/pkg/api/operation"
23+
"k8s.io/apimachinery/pkg/util/validation/field"
24+
)
25+
26+
// CompareFunc is a function that compares two values of the same type.
27+
type CompareFunc[T any] func(T, T) bool
28+
29+
// EachSliceVal validates each element of newSlice with the specified
30+
// validation function. The comparison function is used to find the
31+
// corresponding value in oldSlice. The value-type of the slices is assumed to
32+
// not be nilable.
33+
func EachSliceVal[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newSlice, oldSlice []T,
34+
cmp CompareFunc[T], validator ValidateFunc[*T]) field.ErrorList {
35+
var errs field.ErrorList
36+
for i, val := range newSlice {
37+
var old *T
38+
if cmp != nil && len(oldSlice) > 0 {
39+
old = lookup(oldSlice, val, cmp)
40+
}
41+
errs = append(errs, validator(ctx, op, fldPath.Index(i), &val, old)...)
42+
}
43+
return errs
44+
}
45+
46+
// EachSliceValNilable validates each element of newSlice with the specified
47+
// validation function. The comparison function is used to find the
48+
// corresponding value in oldSlice. The value-type of the slices is assumed to
49+
// be nilable.
50+
func EachSliceValNilable[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newSlice, oldSlice []T,
51+
cmp CompareFunc[T], validator ValidateFunc[T]) field.ErrorList {
52+
var errs field.ErrorList
53+
for i, val := range newSlice {
54+
var old T
55+
if cmp != nil && len(oldSlice) > 0 {
56+
p := lookup(oldSlice, val, cmp)
57+
if p != nil {
58+
old = *p
59+
}
60+
}
61+
errs = append(errs, validator(ctx, op, fldPath.Index(i), val, old)...)
62+
}
63+
return errs
64+
}
65+
66+
// lookup returns a pointer to the first element in the list that matches the
67+
// target, according to the provided comparison function, or else nil.
68+
func lookup[T any](list []T, target T, cmp func(T, T) bool) *T {
69+
for i := range list {
70+
if cmp(list[i], target) {
71+
return &list[i]
72+
}
73+
}
74+
return nil
75+
}
76+
77+
// EachMapVal validates each element of newMap with the specified validation
78+
// function and, if the corresponding key is found in oldMap, the old value.
79+
// The value-type of the slices is assumed to not be nilable.
80+
func EachMapVal[K ~string, V any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]V,
81+
validator ValidateFunc[*V]) field.ErrorList {
82+
var errs field.ErrorList
83+
for key, val := range newMap {
84+
var old *V
85+
if o, found := oldMap[key]; found {
86+
old = &o
87+
}
88+
errs = append(errs, validator(ctx, op, fldPath.Key(string(key)), &val, old)...)
89+
}
90+
return errs
91+
}
92+
93+
// EachMapValNilable validates each element of newMap with the specified
94+
// validation function and, if the corresponding key is found in oldMap, the
95+
// old value. The value-type of the slices is assumed to be nilable.
96+
func EachMapValNilable[K ~string, V any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]V,
97+
validator ValidateFunc[V]) field.ErrorList {
98+
var errs field.ErrorList
99+
for key, val := range newMap {
100+
var old V
101+
if o, found := oldMap[key]; found {
102+
old = o
103+
}
104+
errs = append(errs, validator(ctx, op, fldPath.Key(string(key)), val, old)...)
105+
}
106+
return errs
107+
}
108+
109+
// EachMapKey validates each element of newMap with the specified
110+
// validation function. The oldMap argument is not used.
111+
func EachMapKey[K ~string, T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]T,
112+
validator ValidateFunc[*K]) field.ErrorList {
113+
var errs field.ErrorList
114+
for key := range newMap {
115+
// Note: the field path is the field, not the key.
116+
errs = append(errs, validator(ctx, op, fldPath, &key, nil)...)
117+
}
118+
return errs
119+
}

0 commit comments

Comments
 (0)