build source
This commit is contained in:
commit
ee1fec43ed
4171 changed files with 1351288 additions and 0 deletions
64
vendor/k8s.io/apimachinery/pkg/api/validate/README.md
generated
vendored
Normal file
64
vendor/k8s.io/apimachinery/pkg/api/validate/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# API validation
|
||||
|
||||
This package holds functions which validate fields and types in the Kubernetes
|
||||
API. It may be useful beyond API validation, but this is the primary goal.
|
||||
|
||||
Most of the public functions here have signatures which adhere to the following
|
||||
pattern, which is assumed by automation and code-generation:
|
||||
|
||||
```
|
||||
import (
|
||||
"context"
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func <Name>(ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue <ValueType>, <OtherArgs...>) field.ErrorList
|
||||
```
|
||||
|
||||
The name of validator functions should consider that callers will generally be
|
||||
spelling out the package name and the function name, and so should aim for
|
||||
legibility. E.g. `validate.Concept()`.
|
||||
|
||||
The `ctx` argument is Go's usual Context.
|
||||
|
||||
The `opCtx` argument provides information about the API operation in question.
|
||||
|
||||
The `fldPath` argument indicates the path to the field in question, to be used
|
||||
in errors.
|
||||
|
||||
The `value` and `oldValue` arguments are the thing(s) being validated. For
|
||||
CREATE operations (`opCtx.Operation == operation.Create`), the `oldValue`
|
||||
argument will be nil. Many validators functions only look at the current value
|
||||
(`value`) and disregard `oldValue`.
|
||||
|
||||
The `value` and `oldValue` arguments are always nilable - pointers to primitive
|
||||
types, slices of any type, or maps of any type. Validator functions should
|
||||
avoid dereferencing nil. Callers are expected to not pass a nil `value` unless the
|
||||
API field itself was nilable. `oldValue` is always nil for CREATE operations and
|
||||
is also nil for UPDATE operations if the `value` is not correlated with an `oldValue`.
|
||||
|
||||
Simple content-validators may have no `<OtherArgs>`, but validator functions
|
||||
may take additional arguments. Some validator functions will be built as
|
||||
generics, e.g. to allow any integer type or to handle arbitrary slices.
|
||||
|
||||
Examples:
|
||||
|
||||
```
|
||||
// NonEmpty validates that a string is not empty.
|
||||
func NonEmpty(ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *string) field.ErrorList
|
||||
|
||||
// Even validates that a slice has an even number of items.
|
||||
func Even[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ []T) field.ErrorList
|
||||
|
||||
// KeysMaxLen validates that all of the string keys in a map are under the
|
||||
// specified length.
|
||||
func KeysMaxLen[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ map[string]T, maxLen int) field.ErrorList
|
||||
```
|
||||
|
||||
Validator functions always return an `ErrorList` where each item is a distinct
|
||||
validation failure and a zero-length return value (not just nil) indicates
|
||||
success.
|
||||
|
||||
Good validation failure messages follow the Kubernetes API conventions, for
|
||||
example using "must" instead of "should".
|
||||
28
vendor/k8s.io/apimachinery/pkg/api/validate/common.go
generated
vendored
Normal file
28
vendor/k8s.io/apimachinery/pkg/api/validate/common.go
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
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"
|
||||
)
|
||||
|
||||
// ValidateFunc is a function that validates a value, possibly considering the
|
||||
// old value (if any).
|
||||
type ValidateFunc[T any] func(ctx context.Context, op operation.Operation, fldPath *field.Path, newValue, oldValue T) field.ErrorList
|
||||
32
vendor/k8s.io/apimachinery/pkg/api/validate/constraints/constraints.go
generated
vendored
Normal file
32
vendor/k8s.io/apimachinery/pkg/api/validate/constraints/constraints.go
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package constraints
|
||||
|
||||
// Signed is a constraint that permits any signed integer type.
|
||||
type Signed interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64
|
||||
}
|
||||
|
||||
// Unsigned is a constraint that permits any unsigned integer type.
|
||||
type Unsigned interface {
|
||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
||||
}
|
||||
|
||||
// Integer is a constraint that permits any integer type.
|
||||
type Integer interface {
|
||||
Signed | Unsigned
|
||||
}
|
||||
62
vendor/k8s.io/apimachinery/pkg/api/validate/content/decimal_int.go
generated
vendored
Normal file
62
vendor/k8s.io/apimachinery/pkg/api/validate/content/decimal_int.go
generated
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package content
|
||||
|
||||
const decimalIntegerErrMsg string = "must be a valid decimal integer in canonical form"
|
||||
|
||||
// IsDecimalInteger validates that a string represents a decimal integer in strict canonical form.
|
||||
// This means the string must be formatted exactly as a human would naturally write an integer,
|
||||
// without any programming language conventions like leading zeros, plus signs, or alternate bases.
|
||||
//
|
||||
// valid values:"0" or Non-zero integers (i.e., "123", "-456") where the first digit is 1-9,
|
||||
// followed by any digits 0-9.
|
||||
//
|
||||
// This validator is stricter than strconv.ParseInt, which accepts leading zeros values (i.e, "0700")
|
||||
// and interprets them as decimal 700, potentially causing confusion with octal notation.
|
||||
func IsDecimalInteger(value string) []string {
|
||||
n := len(value)
|
||||
if n == 0 {
|
||||
return []string{EmptyError()}
|
||||
}
|
||||
|
||||
i := 0
|
||||
if value[0] == '-' {
|
||||
if n == 1 {
|
||||
return []string{decimalIntegerErrMsg}
|
||||
}
|
||||
i = 1
|
||||
}
|
||||
|
||||
if value[i] == '0' {
|
||||
if n == 1 && i == 0 {
|
||||
return nil
|
||||
}
|
||||
return []string{decimalIntegerErrMsg}
|
||||
}
|
||||
|
||||
if value[i] < '1' || value[i] > '9' {
|
||||
return []string{decimalIntegerErrMsg}
|
||||
}
|
||||
|
||||
for i++; i < n; i++ {
|
||||
if value[i] < '0' || value[i] > '9' {
|
||||
return []string{decimalIntegerErrMsg}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
101
vendor/k8s.io/apimachinery/pkg/api/validate/content/dns.go
generated
vendored
Normal file
101
vendor/k8s.io/apimachinery/pkg/api/validate/content/dns.go
generated
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package content
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
|
||||
|
||||
const dns1123LabelErrMsg string = "a lowercase RFC 1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character"
|
||||
|
||||
// DNS1123LabelMaxLength is a label's max length in DNS (RFC 1123)
|
||||
const DNS1123LabelMaxLength int = 63
|
||||
|
||||
var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$")
|
||||
|
||||
// IsDNS1123Label tests for a string that conforms to the definition of a label in
|
||||
// DNS (RFC 1123).
|
||||
func IsDNS1123Label(value string) []string {
|
||||
var errs []string
|
||||
if len(value) > DNS1123LabelMaxLength {
|
||||
errs = append(errs, MaxLenError(DNS1123LabelMaxLength))
|
||||
}
|
||||
if !dns1123LabelRegexp.MatchString(value) {
|
||||
if dns1123SubdomainRegexp.MatchString(value) {
|
||||
// It was a valid subdomain and not a valid label. Since we
|
||||
// already checked length, it must be dots.
|
||||
errs = append(errs, "must not contain dots")
|
||||
} else {
|
||||
errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc"))
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*"
|
||||
const dns1123SubdomainFmtCaseless string = "(?i)" + dns1123SubdomainFmt
|
||||
const dns1123SubdomainErrorMsg string = "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
|
||||
const dns1123SubdomainCaselessErrorMsg string = "an RFC 1123 subdomain must consist of alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
|
||||
|
||||
// DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123)
|
||||
const DNS1123SubdomainMaxLength int = 253
|
||||
|
||||
var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
|
||||
var dns1123SubdomainCaselessRegexp = regexp.MustCompile("^" + dns1123SubdomainFmtCaseless + "$")
|
||||
|
||||
// IsDNS1123Subdomain tests for a string that conforms to the definition of a
|
||||
// subdomain in DNS (RFC 1123) lowercase.
|
||||
func IsDNS1123Subdomain(value string) []string {
|
||||
return isDNS1123Subdomain(value, false)
|
||||
}
|
||||
|
||||
// IsDNS1123SubdomainCaseless tests for a string that conforms to the definition of a
|
||||
// subdomain in DNS (RFC 1123).
|
||||
//
|
||||
// Deprecated: API validation should never be caseless. Caseless validation is a vector
|
||||
// for bugs and failed uniqueness assumptions. For example, names like "foo.com" and
|
||||
// "FOO.COM" are both accepted as valid, but they are typically not treated as equal by
|
||||
// consumers (e.g. CSI and DRA driver names). This fails the "least surprise" principle and
|
||||
// can cause inconsistent behaviors.
|
||||
//
|
||||
// Note: This allows uppercase names but is not caseless — uppercase and lowercase are
|
||||
// treated as different values. Use IsDNS1123Subdomain for strict, lowercase validation
|
||||
// instead.
|
||||
func IsDNS1123SubdomainCaseless(value string) []string {
|
||||
return isDNS1123Subdomain(value, true)
|
||||
}
|
||||
|
||||
func isDNS1123Subdomain(value string, caseless bool) []string {
|
||||
var errs []string
|
||||
if len(value) > DNS1123SubdomainMaxLength {
|
||||
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
|
||||
}
|
||||
errorMsg := dns1123SubdomainErrorMsg
|
||||
example := "example.com"
|
||||
regexp := dns1123SubdomainRegexp
|
||||
if caseless {
|
||||
errorMsg = dns1123SubdomainCaselessErrorMsg
|
||||
example = "Example.com"
|
||||
regexp = dns1123SubdomainCaselessRegexp
|
||||
}
|
||||
if !regexp.MatchString(value) {
|
||||
errs = append(errs, RegexError(errorMsg, dns1123SubdomainFmt, example))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
66
vendor/k8s.io/apimachinery/pkg/api/validate/content/errors.go
generated
vendored
Normal file
66
vendor/k8s.io/apimachinery/pkg/api/validate/content/errors.go
generated
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
Copyright 2014 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 content
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/validate/constraints"
|
||||
)
|
||||
|
||||
// MinError returns a string explanation of a "must be greater than or equal"
|
||||
// validation failure.
|
||||
func MinError[T constraints.Integer](min T) string {
|
||||
return fmt.Sprintf("must be greater than or equal to %d", min)
|
||||
}
|
||||
|
||||
// MaxLenError returns a string explanation of a "string too long" validation
|
||||
// failure.
|
||||
func MaxLenError(length int) string {
|
||||
return fmt.Sprintf("must be no more than %d bytes", length)
|
||||
}
|
||||
|
||||
// EmptyError returns a string explanation of an "empty string" validation.
|
||||
func EmptyError() string {
|
||||
return "must be non-empty"
|
||||
}
|
||||
|
||||
// RegexError returns a string explanation of a regex validation failure.
|
||||
func RegexError(msg string, re string, examples ...string) string {
|
||||
if len(examples) == 0 {
|
||||
return msg + " (regex used for validation is '" + re + "')"
|
||||
}
|
||||
msg += " (e.g. "
|
||||
for i := range examples {
|
||||
if i > 0 {
|
||||
msg += " or "
|
||||
}
|
||||
msg += "'" + examples[i] + "', "
|
||||
}
|
||||
msg += "regex used for validation is '" + re + "')"
|
||||
return msg
|
||||
}
|
||||
|
||||
// NEQError returns a string explanation of a "must not be equal to" validation failure.
|
||||
func NEQError[T any](disallowed T) string {
|
||||
format := "%v"
|
||||
if reflect.ValueOf(disallowed).Kind() == reflect.String {
|
||||
format = "%q"
|
||||
}
|
||||
return fmt.Sprintf("must not be equal to "+format, disallowed)
|
||||
}
|
||||
35
vendor/k8s.io/apimachinery/pkg/api/validate/content/identifier.go
generated
vendored
Normal file
35
vendor/k8s.io/apimachinery/pkg/api/validate/content/identifier.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package content
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
const cIdentifierFmt string = "[A-Za-z_][A-Za-z0-9_]*"
|
||||
const identifierErrMsg string = "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_'"
|
||||
|
||||
var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$")
|
||||
|
||||
// IsCIdentifier tests for a string that conforms the definition of an identifier
|
||||
// in C. This checks the format, but not the length.
|
||||
func IsCIdentifier(value string) []string {
|
||||
if !cIdentifierRegexp.MatchString(value) {
|
||||
return []string{RegexError(identifierErrMsg, cIdentifierFmt, "my_name", "MY_NAME", "MyName")}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
101
vendor/k8s.io/apimachinery/pkg/api/validate/content/kube.go
generated
vendored
Normal file
101
vendor/k8s.io/apimachinery/pkg/api/validate/content/kube.go
generated
vendored
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package content
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const labelKeyCharFmt string = "[A-Za-z0-9]"
|
||||
const labelKeyExtCharFmt string = "[-A-Za-z0-9_.]"
|
||||
const labelKeyFmt string = "(" + labelKeyCharFmt + labelKeyExtCharFmt + "*)?" + labelKeyCharFmt
|
||||
const labelKeyErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
|
||||
const labelKeyMaxLength int = 63
|
||||
|
||||
var labelKeyRegexp = regexp.MustCompile("^" + labelKeyFmt + "$")
|
||||
|
||||
// IsQualifiedName tests whether the value passed is what Kubernetes calls a
|
||||
// "qualified name", which is the same as a label key.
|
||||
//
|
||||
// Deprecated: use IsLabelKey instead.
|
||||
var IsQualifiedName = IsLabelKey
|
||||
|
||||
// IsLabelKey tests whether the value passed is a valid label key. This format
|
||||
// is used to validate many fields in the Kubernetes API.
|
||||
// Label keys consist of an optional prefix and a name, separated by a '/'.
|
||||
// If the value is not valid, a list of error strings is returned. Otherwise, an
|
||||
// empty list (or nil) is returned.
|
||||
func IsLabelKey(value string) []string {
|
||||
var errs []string
|
||||
parts := strings.Split(value, "/")
|
||||
var name string
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
name = parts[0]
|
||||
case 2:
|
||||
var prefix string
|
||||
prefix, name = parts[0], parts[1]
|
||||
if len(prefix) == 0 {
|
||||
errs = append(errs, "prefix part "+EmptyError())
|
||||
} else if msgs := IsDNS1123Subdomain(prefix); len(msgs) != 0 {
|
||||
errs = append(errs, prefixEach(msgs, "prefix part ")...)
|
||||
}
|
||||
default:
|
||||
return append(errs, "a valid label key "+RegexError(labelKeyErrMsg, labelKeyFmt, "MyName", "my.name", "123-abc")+
|
||||
" with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')")
|
||||
}
|
||||
|
||||
if len(name) == 0 {
|
||||
errs = append(errs, "name part "+EmptyError())
|
||||
} else if len(name) > labelKeyMaxLength {
|
||||
errs = append(errs, "name part "+MaxLenError(labelKeyMaxLength))
|
||||
}
|
||||
if !labelKeyRegexp.MatchString(name) {
|
||||
errs = append(errs, "name part "+RegexError(labelKeyErrMsg, labelKeyFmt, "MyName", "my.name", "123-abc"))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
const labelValueFmt string = "(" + labelKeyFmt + ")?"
|
||||
const labelValueErrMsg string = "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
|
||||
|
||||
// LabelValueMaxLength is a label's max length
|
||||
const LabelValueMaxLength int = 63
|
||||
|
||||
var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$")
|
||||
|
||||
// IsLabelValue tests whether the value passed is a valid label value. If
|
||||
// the value is not valid, a list of error strings is returned. Otherwise an
|
||||
// empty list (or nil) is returned.
|
||||
func IsLabelValue(value string) []string {
|
||||
var errs []string
|
||||
if len(value) > LabelValueMaxLength {
|
||||
errs = append(errs, MaxLenError(LabelValueMaxLength))
|
||||
}
|
||||
if !labelValueRegexp.MatchString(value) {
|
||||
errs = append(errs, RegexError(labelValueErrMsg, labelValueFmt, "MyValue", "my_value", "12345"))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
func prefixEach(msgs []string, prefix string) []string {
|
||||
for i := range msgs {
|
||||
msgs[i] = prefix + msgs[i]
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
50
vendor/k8s.io/apimachinery/pkg/api/validate/doc.go
generated
vendored
Normal file
50
vendor/k8s.io/apimachinery/pkg/api/validate/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
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 holds API validation functions which are designed for use
|
||||
// with the k8s.io/code-generator/cmd/validation-gen tool. Each validation
|
||||
// function has a similar fingerprint:
|
||||
//
|
||||
// func <Name>(ctx context.Context,
|
||||
// op operation.Operation,
|
||||
// fldPath *field.Path,
|
||||
// value, oldValue <nilable type>,
|
||||
// <other args...>) field.ErrorList
|
||||
//
|
||||
// The value and oldValue arguments will always be a nilable type. If the
|
||||
// original value was a string, these will be a *string. If the original value
|
||||
// was a slice or map, these will be the same slice or map type.
|
||||
//
|
||||
// For a CREATE operation, the oldValue will always be nil. For an UPDATE
|
||||
// operation, either value or oldValue may be nil, e.g. when adding or removing
|
||||
// a value in a list-map. Validators which care about UPDATE operations should
|
||||
// look at the opCtx argument to know which operation is being executed.
|
||||
//
|
||||
// Tightened validation (also known as ratcheting validation) is supported by
|
||||
// defining a new validation function. For example:
|
||||
//
|
||||
// func TightenedMaxLength(ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *string) field.ErrorList {
|
||||
// if oldValue != nil && len(MaxLength(ctx, op, fldPath, oldValue, nil)) > 0 {
|
||||
// // old value is not valid, so this value skips the tightened validation
|
||||
// return nil
|
||||
// }
|
||||
// return MaxLength(ctx, op, fldPath, value, nil)
|
||||
// }
|
||||
//
|
||||
// In general, we cannot distinguish a non-specified slice or map from one that
|
||||
// is specified but empty. Validators should not rely on nil values, but use
|
||||
// len() instead.
|
||||
package validate
|
||||
186
vendor/k8s.io/apimachinery/pkg/api/validate/each.go
generated
vendored
Normal file
186
vendor/k8s.io/apimachinery/pkg/api/validate/each.go
generated
vendored
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
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"
|
||||
"sort"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// MatchFunc is a function that compares two values of the same type,
|
||||
// according to some criteria, and returns true if they match.
|
||||
type MatchFunc[T any] func(T, T) bool
|
||||
|
||||
// EachSliceVal performs validation on each element of newSlice using the provided validation function.
|
||||
//
|
||||
// For update operations, the match function finds corresponding values in oldSlice for each
|
||||
// value in newSlice. This comparison can be either full or partial (e.g., matching only
|
||||
// specific struct fields that serve as a unique identifier). If match is nil, validation
|
||||
// proceeds without considering old values, and the equiv function is not used.
|
||||
//
|
||||
// For update operations, the equiv function checks if a new value is equivalent to its
|
||||
// corresponding old value, enabling validation ratcheting. If equiv is nil but match is
|
||||
// provided, the match function is assumed to perform full value comparison.
|
||||
//
|
||||
// Note: The slice element type must be non-nilable.
|
||||
func EachSliceVal[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newSlice, oldSlice []T,
|
||||
match, equiv MatchFunc[T], validator ValidateFunc[*T]) field.ErrorList {
|
||||
var errs field.ErrorList
|
||||
for i, val := range newSlice {
|
||||
var old *T
|
||||
if match != nil && len(oldSlice) > 0 {
|
||||
old = lookup(oldSlice, val, match)
|
||||
}
|
||||
// If the operation is an update, for validation ratcheting, skip re-validating if the old
|
||||
// value exists and either:
|
||||
// 1. The match function provides full comparison (equiv is nil)
|
||||
// 2. The equiv function confirms the values are equivalent (either directly or semantically)
|
||||
//
|
||||
// The equiv function provides equality comparison when match uses partial comparison.
|
||||
if op.Type == operation.Update && old != nil && (equiv == nil || equiv(val, *old)) {
|
||||
continue
|
||||
}
|
||||
errs = append(errs, validator(ctx, op, fldPath.Index(i), &val, old)...)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// lookup returns a pointer to the first element in the list that matches the
|
||||
// target, according to the provided comparison function, or else nil.
|
||||
func lookup[T any](list []T, target T, match MatchFunc[T]) *T {
|
||||
for i := range list {
|
||||
if match(list[i], target) {
|
||||
return &list[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EachMapVal validates each value in newMap using the specified validation
|
||||
// function, passing the corresponding old value from oldMap if the key exists in oldMap.
|
||||
// For update operations, it implements validation ratcheting by skipping validation
|
||||
// when the old value exists and the equiv function confirms the values are equivalent.
|
||||
// The value-type of the map is assumed to not be nilable.
|
||||
// If equiv is nil, value-based ratcheting is disabled and all values will be validated.
|
||||
func EachMapVal[K ~string, V any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]V,
|
||||
equiv MatchFunc[V], validator ValidateFunc[*V]) field.ErrorList {
|
||||
var errs field.ErrorList
|
||||
for key, val := range newMap {
|
||||
var old *V
|
||||
if o, found := oldMap[key]; found {
|
||||
old = &o
|
||||
}
|
||||
// If the operation is an update, for validation ratcheting, skip re-validating if the old
|
||||
// value is found and the equiv function confirms the values are equivalent.
|
||||
if op.Type == operation.Update && old != nil && equiv != nil && equiv(val, *old) {
|
||||
continue
|
||||
}
|
||||
errs = append(errs, validator(ctx, op, fldPath.Key(string(key)), &val, old)...)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// EachMapKey validates each element of newMap with the specified
|
||||
// validation function.
|
||||
func EachMapKey[K ~string, T any](ctx context.Context, op operation.Operation, fldPath *field.Path, newMap, oldMap map[K]T,
|
||||
validator ValidateFunc[*K]) field.ErrorList {
|
||||
var errs field.ErrorList
|
||||
for key := range newMap {
|
||||
var old *K
|
||||
if _, found := oldMap[key]; found {
|
||||
old = &key
|
||||
}
|
||||
// If the operation is an update, for validation ratcheting, skip re-validating if
|
||||
// the key is found in oldMap.
|
||||
if op.Type == operation.Update && old != nil {
|
||||
continue
|
||||
}
|
||||
// Note: the field path is the field, not the key.
|
||||
errs = append(errs, validator(ctx, op, fldPath, &key, nil)...)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// Unique verifies that each element of newSlice is unique, according to the
|
||||
// match function. It compares every element of the slice with every other
|
||||
// element and returns errors for non-unique items.
|
||||
func Unique[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, newSlice, _ []T, match MatchFunc[T]) field.ErrorList {
|
||||
var dups []int
|
||||
for i, val := range newSlice {
|
||||
for j := i + 1; j < len(newSlice); j++ {
|
||||
other := newSlice[j]
|
||||
if match(val, other) {
|
||||
if dups == nil {
|
||||
dups = make([]int, 0, len(newSlice))
|
||||
}
|
||||
if lookup(dups, j, func(a, b int) bool { return a == b }) == nil {
|
||||
dups = append(dups, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errs field.ErrorList
|
||||
sort.Ints(dups)
|
||||
for _, i := range dups {
|
||||
var val any = newSlice[i]
|
||||
// TODO: we don't want the whole item to be logged in the error, just
|
||||
// the key(s). Unfortunately, the way errors are rendered, it comes out
|
||||
// as something like "map[string]any{...}" which is not very nice. Once
|
||||
// that is fixed, we can consider adding a way for this function to
|
||||
// specify that just the keys should be rendered in the error.
|
||||
errs = append(errs, field.Duplicate(fldPath.Index(i), val))
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// SemanticDeepEqual is a MatchFunc that uses equality.Semantic.DeepEqual to
|
||||
// compare two values.
|
||||
// This wrapper is needed because MatchFunc requires a function that takes two
|
||||
// arguments of specific type T, while equality.Semantic.DeepEqual takes
|
||||
// arguments of type interface{}/any. The wrapper satisfies the type
|
||||
// constraints of MatchFunc while leveraging the underlying semantic equality
|
||||
// logic. It can be used by any other function that needs to call DeepEqual.
|
||||
func SemanticDeepEqual[T any](a, b T) bool {
|
||||
return equality.Semantic.DeepEqual(a, b)
|
||||
}
|
||||
|
||||
// DirectEqual is a MatchFunc that uses the == operator to compare two values.
|
||||
// It can be used by any other function that needs to compare two values
|
||||
// directly.
|
||||
func DirectEqual[T comparable](a, b T) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
// DirectEqualPtr is a MatchFunc that dereferences two pointers and uses the ==
|
||||
// operator to compare the values. If both pointers are nil, it returns true.
|
||||
// If one pointer is nil and the other is not, it returns false.
|
||||
// It can be used by any other function that needs to compare two pointees
|
||||
// directly.
|
||||
func DirectEqualPtr[T comparable](a, b *T) bool {
|
||||
if a == b {
|
||||
return true
|
||||
}
|
||||
if a == nil || b == nil {
|
||||
return false
|
||||
}
|
||||
return *a == *b
|
||||
}
|
||||
74
vendor/k8s.io/apimachinery/pkg/api/validate/enum.go
generated
vendored
Normal file
74
vendor/k8s.io/apimachinery/pkg/api/validate/enum.go
generated
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
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"
|
||||
"slices"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// Enum verifies that a given value is a member of a set of enum values.
|
||||
// Exclude Rules that apply when options are enabled or disabled are also considered.
|
||||
// If ANY exclude rule matches for a value, that value is excluded from the enum when validating.
|
||||
func Enum[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T, validValues sets.Set[T], exclusions []EnumExclusion[T]) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if !validValues.Has(*value) || isExcluded(op, exclusions, *value) {
|
||||
return field.ErrorList{field.NotSupported[T](fldPath, *value, supportedValues(op, validValues, exclusions))}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// supportedValues returns a sorted list of supported values.
|
||||
// Excluded enum values are not included in the list.
|
||||
func supportedValues[T ~string](op operation.Operation, values sets.Set[T], exclusions []EnumExclusion[T]) []T {
|
||||
res := make([]T, 0, len(values))
|
||||
for key := range values {
|
||||
if isExcluded(op, exclusions, key) {
|
||||
continue
|
||||
}
|
||||
res = append(res, key)
|
||||
}
|
||||
slices.Sort(res)
|
||||
return res
|
||||
}
|
||||
|
||||
// EnumExclusion represents a single enum exclusion rule.
|
||||
type EnumExclusion[T ~string] struct {
|
||||
// Value specifies the enum value to be conditionally excluded.
|
||||
Value T
|
||||
// ExcludeWhen determines the condition for exclusion.
|
||||
// If true, the value is excluded if the option is present.
|
||||
// If false, the value is excluded if the option is NOT present.
|
||||
ExcludeWhen bool
|
||||
// Option is the name of the feature option that controls the exclusion.
|
||||
Option string
|
||||
}
|
||||
|
||||
func isExcluded[T ~string](op operation.Operation, exclusions []EnumExclusion[T], value T) bool {
|
||||
for _, rule := range exclusions {
|
||||
if rule.Value == value && rule.ExcludeWhen == op.HasOption(rule.Option) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
38
vendor/k8s.io/apimachinery/pkg/api/validate/equality.go
generated
vendored
Normal file
38
vendor/k8s.io/apimachinery/pkg/api/validate/equality.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/api/validate/content"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// NEQ validates that the specified comparable value is not equal to the disallowed value.
|
||||
func NEQ[T comparable](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, disallowed T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if *value == disallowed {
|
||||
return field.ErrorList{
|
||||
field.Invalid(fldPath, *value, content.NEQError(disallowed)).WithOrigin("neq"),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
40
vendor/k8s.io/apimachinery/pkg/api/validate/immutable.go
generated
vendored
Normal file
40
vendor/k8s.io/apimachinery/pkg/api/validate/immutable.go
generated
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// Immutable verifies that the specified value has not changed in the course of
|
||||
// an update operation. It does nothing if the old value is not provided.
|
||||
//
|
||||
// This function unconditionally returns a validation error as it
|
||||
// relies on the default ratcheting mechanism to only be called when a
|
||||
// change to the field has already been detected. This avoids a redundant
|
||||
// equivalence check across ratcheting and this function.
|
||||
func Immutable[T any](_ context.Context, op operation.Operation, fldPath *field.Path, _, _ T) field.ErrorList {
|
||||
if op.Type != operation.Update {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{
|
||||
field.Invalid(fldPath, nil, "field is immutable").WithOrigin("immutable"),
|
||||
}
|
||||
}
|
||||
77
vendor/k8s.io/apimachinery/pkg/api/validate/item.go
generated
vendored
Normal file
77
vendor/k8s.io/apimachinery/pkg/api/validate/item.go
generated
vendored
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
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"
|
||||
)
|
||||
|
||||
// MatchItemFn takes a pointer to an item and returns true if it matches the criteria.
|
||||
type MatchItemFn[T any] func(*T) bool
|
||||
|
||||
// SliceItem finds the first item in newList that satisfies the match function,
|
||||
// and if found, also looks for a matching item in oldList. If the value of the
|
||||
// item is the same as the previous value, as per the equiv function, then no
|
||||
// validation is performed. Otherwise, it invokes 'itemValidator' on these items.
|
||||
//
|
||||
// This function processes only the *first* matching item found in newList. It
|
||||
// assumes that the match functions targets a unique identifier (primary key)
|
||||
// and will match at most one element per list. If this assumption is violated,
|
||||
// changes in list order can lead this function to have inconsistent behavior.
|
||||
//
|
||||
// The fldPath passed to itemValidator is indexed to the matched item's
|
||||
// position in newList.
|
||||
//
|
||||
// This function does not validate items that were removed (present in oldList
|
||||
// but not in newList).
|
||||
func SliceItem[TList ~[]TItem, TItem any](
|
||||
ctx context.Context, op operation.Operation, fldPath *field.Path,
|
||||
newList, oldList TList,
|
||||
matches MatchItemFn[TItem],
|
||||
equiv MatchFunc[TItem],
|
||||
itemValidator func(ctx context.Context, op operation.Operation, fldPath *field.Path, newObj, oldObj *TItem) field.ErrorList,
|
||||
) field.ErrorList {
|
||||
var matchedNew, matchedOld *TItem
|
||||
var newIndex int
|
||||
|
||||
for i := range newList {
|
||||
if matches(&newList[i]) {
|
||||
matchedNew = &newList[i]
|
||||
newIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if matchedNew == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i := range oldList {
|
||||
if matches(&oldList[i]) {
|
||||
matchedOld = &oldList[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if op.Type == operation.Update && matchedOld != nil && equiv(*matchedNew, *matchedOld) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return itemValidator(ctx, op, fldPath.Index(newIndex), matchedNew, matchedOld)
|
||||
}
|
||||
57
vendor/k8s.io/apimachinery/pkg/api/validate/limits.go
generated
vendored
Normal file
57
vendor/k8s.io/apimachinery/pkg/api/validate/limits.go
generated
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
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/api/validate/constraints"
|
||||
"k8s.io/apimachinery/pkg/api/validate/content"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// MaxLength verifies that the specified value is not longer than max
|
||||
// characters.
|
||||
func MaxLength[T ~string](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, max int) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if len(*value) > max {
|
||||
return field.ErrorList{field.TooLong(fldPath, *value, max).WithOrigin("maxLength")}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxItems verifies that the specified slice is not longer than max items.
|
||||
func MaxItems[T any](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ []T, max int) field.ErrorList {
|
||||
if len(value) > max {
|
||||
return field.ErrorList{field.TooMany(fldPath, len(value), max).WithOrigin("maxItems")}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Minimum verifies that the specified value is greater than or equal to min.
|
||||
func Minimum[T constraints.Integer](_ context.Context, _ operation.Operation, fldPath *field.Path, value, _ *T, min T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if *value < min {
|
||||
return field.ErrorList{field.Invalid(fldPath, *value, content.MinError(min)).WithOrigin("minimum")}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
35
vendor/k8s.io/apimachinery/pkg/api/validate/options.go
generated
vendored
Normal file
35
vendor/k8s.io/apimachinery/pkg/api/validate/options.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// IfOption conditionally evaluates a validation function. If the option and enabled are both true the validator
|
||||
// is called. If the option and enabled are both false the validator is called. Otherwise, the validator is not called.
|
||||
func IfOption[T any](ctx context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T,
|
||||
optionName string, enabled bool, validator func(context.Context, operation.Operation, *field.Path, *T, *T) field.ErrorList,
|
||||
) field.ErrorList {
|
||||
if op.HasOption(optionName) == enabled {
|
||||
return validator(ctx, op, fldPath, value, oldValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
133
vendor/k8s.io/apimachinery/pkg/api/validate/required.go
generated
vendored
Normal file
133
vendor/k8s.io/apimachinery/pkg/api/validate/required.go
generated
vendored
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, "")}
|
||||
}
|
||||
|
||||
// ForbiddenMap 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")}
|
||||
}
|
||||
290
vendor/k8s.io/apimachinery/pkg/api/validate/strfmt.go
generated
vendored
Normal file
290
vendor/k8s.io/apimachinery/pkg/api/validate/strfmt.go
generated
vendored
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/api/validate/content"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
const (
|
||||
uuidErrorMessage = "must be a lowercase UUID in 8-4-4-4-12 format"
|
||||
defaultResourceRequestsPrefix = "requests."
|
||||
// Default namespace prefix.
|
||||
resourceDefaultNamespacePrefix = "kubernetes.io/"
|
||||
resourceDeviceMaxLength = 32
|
||||
)
|
||||
|
||||
// ShortName verifies that the specified value is a valid "short name"
|
||||
// (sometimes known as a "DNS label").
|
||||
// - must not be empty
|
||||
// - must be less than 64 characters long
|
||||
// - must start and end with lower-case alphanumeric characters
|
||||
// - must contain only lower-case alphanumeric characters or dashes
|
||||
//
|
||||
// All errors returned by this function will be "invalid" type errors. If the
|
||||
// caller wants better errors, it must take responsibility for checking things
|
||||
// like required/optional and max-length.
|
||||
func ShortName[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
var allErrs field.ErrorList
|
||||
for _, msg := range content.IsDNS1123Label((string)(*value)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-short-name"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// LongName verifies that the specified value is a valid "long name"
|
||||
// (sometimes known as a "DNS subdomain").
|
||||
// - must not be empty
|
||||
// - must be less than 254 characters long
|
||||
// - each element must start and end with lower-case alphanumeric characters
|
||||
// - each element must contain only lower-case alphanumeric characters or dashes
|
||||
//
|
||||
// All errors returned by this function will be "invalid" type errors. If the
|
||||
// caller wants better errors, it must take responsibility for checking things
|
||||
// like required/optional and max-length.
|
||||
func LongName[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
var allErrs field.ErrorList
|
||||
for _, msg := range content.IsDNS1123Subdomain((string)(*value)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-long-name"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// LabelKey verifies that the specified value is a valid label key.
|
||||
// A label key is composed of an optional prefix and a name, separated by a '/'.
|
||||
// The name part is required and must:
|
||||
// - be 63 characters or less
|
||||
// - begin and end with an alphanumeric character ([a-z0-9A-Z])
|
||||
// - contain only alphanumeric characters, dashes (-), underscores (_), or dots (.)
|
||||
//
|
||||
// The prefix is optional and must:
|
||||
// - be a DNS subdomain
|
||||
// - be no more than 253 characters
|
||||
func LabelKey[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
var allErrs field.ErrorList
|
||||
for _, msg := range content.IsLabelKey((string)(*value)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-label-key"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// LongNameCaseless verifies that the specified value is a valid "long name"
|
||||
// (sometimes known as a "DNS subdomain"), but is case-insensitive.
|
||||
// - must not be empty
|
||||
// - must be less than 254 characters long
|
||||
// - each element must start and end with alphanumeric characters
|
||||
// - each element must contain only alphanumeric characters or dashes
|
||||
//
|
||||
// Deprecated: Case-insensitive names are not recommended as they can lead to ambiguity
|
||||
// (e.g., 'Foo', 'FOO', and 'foo' would be allowed names for foo). Use LongName for strict, lowercase validation.
|
||||
func LongNameCaseless[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
var allErrs field.ErrorList
|
||||
for _, msg := range content.IsDNS1123SubdomainCaseless((string)(*value)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-long-name-caseless"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// LabelValue verifies that the specified value is a valid label value.
|
||||
// - can be empty
|
||||
// - must be no more than 63 characters
|
||||
// - must start and end with alphanumeric characters
|
||||
// - must contain only alphanumeric characters, dashes, underscores, or dots
|
||||
func LabelValue[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
var allErrs field.ErrorList
|
||||
for _, msg := range content.IsLabelValue((string)(*value)) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, *value, msg).WithOrigin("format=k8s-label-value"))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// UUID verifies that the specified value is a valid UUID (RFC 4122).
|
||||
// - must be 36 characters long
|
||||
// - must be in the normalized form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`
|
||||
// - must use only lowercase hexadecimal characters
|
||||
func UUID[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
val := (string)(*value)
|
||||
if len(val) != 36 {
|
||||
return field.ErrorList{field.Invalid(fldPath, val, uuidErrorMessage).WithOrigin("format=k8s-uuid")}
|
||||
}
|
||||
for idx := 0; idx < len(val); idx++ {
|
||||
character := val[idx]
|
||||
switch idx {
|
||||
case 8, 13, 18, 23:
|
||||
if character != '-' {
|
||||
return field.ErrorList{field.Invalid(fldPath, val, uuidErrorMessage).WithOrigin("format=k8s-uuid")}
|
||||
}
|
||||
default:
|
||||
// should be lower case hexadecimal.
|
||||
if (character < '0' || character > '9') && (character < 'a' || character > 'f') {
|
||||
return field.ErrorList{field.Invalid(fldPath, val, uuidErrorMessage).WithOrigin("format=k8s-uuid")}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResourcePoolName verifies that the specified value is one or more valid "long name"
|
||||
// parts separated by a '/' and no longer than 253 characters.
|
||||
func ResourcePoolName[T ~string](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
val := (string)(*value)
|
||||
var allErrs field.ErrorList
|
||||
if len(val) > 253 {
|
||||
allErrs = append(allErrs, field.TooLong(fldPath, val, 253))
|
||||
}
|
||||
parts := strings.Split(val, "/")
|
||||
for i, part := range parts {
|
||||
if len(part) == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, val, fmt.Sprintf("segment %d: must not be empty", i)))
|
||||
continue
|
||||
}
|
||||
// Note that we are overwriting the origin from the underlying LongName validation.
|
||||
allErrs = append(allErrs, LongName(ctx, op, fldPath, &part, nil).PrefixDetail(fmt.Sprintf("segment %d: ", i))...)
|
||||
}
|
||||
return allErrs.WithOrigin("format=k8s-resource-pool-name")
|
||||
}
|
||||
|
||||
// ExtendedResourceName verifies that the specified value is a valid extended resource name.
|
||||
// An extended resource name is a domain-prefixed name that does not use the "kubernetes.io"
|
||||
// or "requests." prefixes. Must be a valid label key when appended to "requests.", as in quota.
|
||||
//
|
||||
// - must have slash domain and name.
|
||||
// - must not have the "kubernetes.io" domain
|
||||
// - must not have the "requests." prefix
|
||||
// - name must be 63 characters or less
|
||||
// - must be a valid label key when appended to "requests.", as in quota
|
||||
// -- must contain only alphanumeric characters, dashes, underscores, or dots
|
||||
// -- must end with an alphanumeric character
|
||||
func ExtendedResourceName[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
val := string(*value)
|
||||
allErrs := field.ErrorList{}
|
||||
if !strings.Contains(val, "/") {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, val, "a name must be a domain-prefixed path, such as 'example.com/my-prop'"))
|
||||
} else if strings.Contains(val, resourceDefaultNamespacePrefix) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, val, fmt.Sprintf("must not have %q domain", resourceDefaultNamespacePrefix)))
|
||||
}
|
||||
// Ensure extended resource is not type of quota.
|
||||
if strings.HasPrefix(val, defaultResourceRequestsPrefix) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, val, fmt.Sprintf("must not have %q prefix", defaultResourceRequestsPrefix)))
|
||||
}
|
||||
|
||||
// Ensure it satisfies the rules in IsLabelKey() after converted into quota resource name
|
||||
nameForQuota := fmt.Sprintf("%s%s", defaultResourceRequestsPrefix, val)
|
||||
for _, msg := range content.IsLabelKey(nameForQuota) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, val, msg))
|
||||
}
|
||||
return allErrs.WithOrigin("format=k8s-extended-resource-name")
|
||||
}
|
||||
|
||||
// resourcesQualifiedName verifies that the specified value is a valid Kubernetes resources
|
||||
// qualified name.
|
||||
// - must not be empty
|
||||
// - must be composed of an optional prefix and a name, separated by a slash (e.g., "prefix/name")
|
||||
// - the prefix, if specified, must be a DNS subdomain
|
||||
// - the name part must be a C identifier
|
||||
// - the name part must be no more than 32 characters
|
||||
func resourcesQualifiedName[T ~string](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
var allErrs field.ErrorList
|
||||
s := string(*value)
|
||||
parts := strings.Split(s, "/")
|
||||
// TODO: This validation and the corresponding handwritten validation validateQualifiedName in
|
||||
// pkg/apis/resource/validation/validation.go are not validating whether there are more than 1
|
||||
// slash. This should be fixed in both places.
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
allErrs = append(allErrs, validateCIdentifier(parts[0], resourceDeviceMaxLength, fldPath)...)
|
||||
case 2:
|
||||
if len(parts[0]) == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, "", "prefix must not be empty"))
|
||||
} else {
|
||||
if len(parts[0]) > 63 {
|
||||
allErrs = append(allErrs, field.TooLong(fldPath, parts[0], 63))
|
||||
}
|
||||
allErrs = append(allErrs, LongName(ctx, op, fldPath, &parts[0], nil).PrefixDetail("prefix: ")...)
|
||||
}
|
||||
if len(parts[1]) == 0 {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, "", "name must not be empty"))
|
||||
} else {
|
||||
allErrs = append(allErrs, validateCIdentifier(parts[1], resourceDeviceMaxLength, fldPath)...)
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ResourceFullyQualifiedName verifies that the specified value is a valid Kubernetes
|
||||
// fully qualified name.
|
||||
// - must not be empty
|
||||
// - must be composed of a prefix and a name, separated by a slash (e.g., "prefix/name")
|
||||
// - the prefix must be a DNS subdomain
|
||||
// - the name part must be a C identifier
|
||||
// - the name part must be no more than 32 characters
|
||||
func ResourceFullyQualifiedName[T ~string](ctx context.Context, op operation.Operation, fldPath *field.Path, value, _ *T) field.ErrorList {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
var allErrs field.ErrorList
|
||||
s := string(*value)
|
||||
allErrs = append(allErrs, resourcesQualifiedName(ctx, op, fldPath, &s, nil)...)
|
||||
if !strings.Contains(s, "/") {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, s, "a fully qualified name must be a domain and a name separated by a slash"))
|
||||
}
|
||||
return allErrs.WithOrigin("format=k8s-resource-fully-qualified-name")
|
||||
}
|
||||
|
||||
func validateCIdentifier(id string, length int, fldPath *field.Path) field.ErrorList {
|
||||
var allErrs field.ErrorList
|
||||
if len(id) > length {
|
||||
allErrs = append(allErrs, field.TooLong(fldPath, id, length))
|
||||
}
|
||||
for _, msg := range content.IsCIdentifier(id) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, id, msg))
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
53
vendor/k8s.io/apimachinery/pkg/api/validate/subfield.go
generated
vendored
Normal file
53
vendor/k8s.io/apimachinery/pkg/api/validate/subfield.go
generated
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// GetFieldFunc is a function that extracts a field from a type and returns a
|
||||
// nilable value.
|
||||
type GetFieldFunc[Tstruct any, Tfield any] func(*Tstruct) Tfield
|
||||
|
||||
// Subfield validates a subfield of a struct against a validator function. If
|
||||
// the value of the subfield is the same as the previous value, as per the
|
||||
// equiv function, then no validation is performed.
|
||||
//
|
||||
// The fldPath passed to the validator includes the subfield name.
|
||||
func Subfield[Tstruct any, Tfield any](
|
||||
ctx context.Context, op operation.Operation, fldPath *field.Path,
|
||||
newStruct, oldStruct *Tstruct,
|
||||
fldName string, getField GetFieldFunc[Tstruct, Tfield],
|
||||
equiv MatchFunc[Tfield],
|
||||
validator ValidateFunc[Tfield],
|
||||
) field.ErrorList {
|
||||
var errs field.ErrorList
|
||||
newVal := getField(newStruct)
|
||||
var oldVal Tfield
|
||||
if oldStruct != nil {
|
||||
oldVal = getField(oldStruct)
|
||||
}
|
||||
if op.Type == operation.Update && oldStruct != nil && equiv(newVal, oldVal) {
|
||||
return nil
|
||||
}
|
||||
errs = append(errs, validator(ctx, op, fldPath.Child(fldName), newVal, oldVal)...)
|
||||
return errs
|
||||
}
|
||||
35
vendor/k8s.io/apimachinery/pkg/api/validate/testing.go
generated
vendored
Normal file
35
vendor/k8s.io/apimachinery/pkg/api/validate/testing.go
generated
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
Copyright 2014 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"
|
||||
)
|
||||
|
||||
// FixedResult asserts a fixed boolean result. This is mostly useful for
|
||||
// testing.
|
||||
func FixedResult[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ T, result bool, arg string) field.ErrorList {
|
||||
if result {
|
||||
return nil
|
||||
}
|
||||
return field.ErrorList{
|
||||
field.Invalid(fldPath, value, "forced failure: "+arg).WithOrigin("validateFalse"),
|
||||
}
|
||||
}
|
||||
227
vendor/k8s.io/apimachinery/pkg/api/validate/union.go
generated
vendored
Normal file
227
vendor/k8s.io/apimachinery/pkg/api/validate/union.go
generated
vendored
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// ExtractorFn extracts a value from a parent object. Depending on the context,
|
||||
// that could be the value of a field or just whether that field was set or
|
||||
// not.
|
||||
// Note: obj is not guaranteed to be non-nil, need to handle nil obj in the
|
||||
// extractor.
|
||||
type ExtractorFn[T, V any] func(obj T) V
|
||||
|
||||
// UnionValidationOptions configures how union validation behaves
|
||||
type UnionValidationOptions struct {
|
||||
// ErrorForEmpty returns error when no fields are set (nil means no error)
|
||||
ErrorForEmpty func(fldPath *field.Path, allFields []string) *field.Error
|
||||
|
||||
// ErrorForMultiple returns error when multiple fields are set (nil means no error)
|
||||
ErrorForMultiple func(fldPath *field.Path, specifiedFields []string, allFields []string) *field.Error
|
||||
}
|
||||
|
||||
// Union verifies that exactly one member of a union is specified.
|
||||
//
|
||||
// UnionMembership must define all the members of the union.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// var UnionMembershipForABC := validate.NewUnionMembership(
|
||||
// validate.NewUnionMember("a"),
|
||||
// validate.NewUnionMember("b"),
|
||||
// validate.NewUnionMember("c"),
|
||||
// )
|
||||
// func ValidateABC(ctx context.Context, op operation.Operation, fldPath *field.Path, in *ABC) (errs field.ErrorList) {
|
||||
// errs = append(errs, Union(ctx, op, fldPath, in, oldIn, UnionMembershipForABC,
|
||||
// func(in *ABC) bool { return in.A != nil },
|
||||
// func(in *ABC) bool { return in.B != "" },
|
||||
// func(in *ABC) bool { return in.C != 0 },
|
||||
// )...)
|
||||
// return errs
|
||||
// }
|
||||
func Union[T any](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, isSetFns ...ExtractorFn[T, bool]) field.ErrorList {
|
||||
options := UnionValidationOptions{
|
||||
ErrorForEmpty: func(fldPath *field.Path, allFields []string) *field.Error {
|
||||
return field.Invalid(fldPath, "",
|
||||
fmt.Sprintf("must specify one of: %s", strings.Join(allFields, ", ")))
|
||||
},
|
||||
ErrorForMultiple: func(fldPath *field.Path, specifiedFields []string, allFields []string) *field.Error {
|
||||
return field.Invalid(fldPath, fmt.Sprintf("{%s}", strings.Join(specifiedFields, ", ")),
|
||||
fmt.Sprintf("must specify exactly one of: %s", strings.Join(allFields, ", ")))
|
||||
},
|
||||
}
|
||||
|
||||
return unionValidate(op, fldPath, obj, oldObj, union, options, isSetFns...)
|
||||
}
|
||||
|
||||
// DiscriminatedUnion verifies specified union member matches the discriminator.
|
||||
//
|
||||
// UnionMembership must define all the members of the union and the discriminator.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// var UnionMembershipForABC = validate.NewDiscriminatedUnionMembership("type",
|
||||
// validate.NewDiscriminatedUnionMember("a", "A"),
|
||||
// validate.NewDiscriminatedUnionMember("b", "B"),
|
||||
// validate.NewDiscriminatedUnionMember("c", "C"),
|
||||
// )
|
||||
// func ValidateABC(ctx context.Context, op operation.Operation, fldPath *field.Path, in *ABC) (errs field.ErrorList) {
|
||||
// errs = append(errs, DiscriminatedUnion(ctx, op, fldPath, in, oldIn, UnionMembershipForABC,
|
||||
// func(in *ABC) string { return string(in.Type) },
|
||||
// func(in *ABC) bool { return in.A != nil },
|
||||
// func(in *ABC) bool { return in.B != "" },
|
||||
// func(in *ABC) bool { return in.C != 0 },
|
||||
// )...)
|
||||
// return errs
|
||||
// }
|
||||
//
|
||||
// It is not an error for the discriminatorValue to be unknown. That must be
|
||||
// validated on its own.
|
||||
func DiscriminatedUnion[T any, D ~string](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, discriminatorExtractor ExtractorFn[T, D], isSetFns ...ExtractorFn[T, bool]) (errs field.ErrorList) {
|
||||
if len(union.members) != len(isSetFns) {
|
||||
return field.ErrorList{
|
||||
field.InternalError(fldPath,
|
||||
fmt.Errorf("number of extractors (%d) does not match number of union members (%d)",
|
||||
len(isSetFns), len(union.members))),
|
||||
}
|
||||
}
|
||||
var changed bool
|
||||
discriminatorValue := discriminatorExtractor(obj)
|
||||
if op.Type == operation.Update {
|
||||
oldDiscriminatorValue := discriminatorExtractor(oldObj)
|
||||
changed = discriminatorValue != oldDiscriminatorValue
|
||||
}
|
||||
|
||||
for i, fieldIsSet := range isSetFns {
|
||||
member := union.members[i]
|
||||
isDiscriminatedMember := string(discriminatorValue) == member.discriminatorValue
|
||||
newIsSet := fieldIsSet(obj)
|
||||
if op.Type == operation.Update && !changed {
|
||||
oldIsSet := fieldIsSet(oldObj)
|
||||
changed = changed || newIsSet != oldIsSet
|
||||
}
|
||||
if newIsSet && !isDiscriminatedMember {
|
||||
errs = append(errs, field.Invalid(fldPath.Child(member.fieldName), "",
|
||||
fmt.Sprintf("may only be specified when `%s` is %q", union.discriminatorName, member.discriminatorValue)))
|
||||
} else if !newIsSet && isDiscriminatedMember {
|
||||
errs = append(errs, field.Invalid(fldPath.Child(member.fieldName), "",
|
||||
fmt.Sprintf("must be specified when `%s` is %q", union.discriminatorName, discriminatorValue)))
|
||||
}
|
||||
}
|
||||
// If the union discriminator and membership is unchanged, we don't need to
|
||||
// re-validate.
|
||||
if op.Type == operation.Update && !changed {
|
||||
return nil
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// UnionMember represents a member of a union.
|
||||
type UnionMember struct {
|
||||
fieldName string
|
||||
discriminatorValue string
|
||||
}
|
||||
|
||||
// NewUnionMember returns a new UnionMember for the given field name.
|
||||
func NewUnionMember(fieldName string) UnionMember {
|
||||
return UnionMember{fieldName: fieldName}
|
||||
}
|
||||
|
||||
// NewDiscriminatedUnionMember returns a new UnionMember for the given field
|
||||
// name and discriminator value.
|
||||
func NewDiscriminatedUnionMember(fieldName, discriminatorValue string) UnionMember {
|
||||
return UnionMember{fieldName: fieldName, discriminatorValue: discriminatorValue}
|
||||
}
|
||||
|
||||
// UnionMembership represents an ordered list of field union memberships.
|
||||
type UnionMembership struct {
|
||||
discriminatorName string
|
||||
members []UnionMember
|
||||
}
|
||||
|
||||
// NewUnionMembership returns a new UnionMembership for the given list of members.
|
||||
// Member names must be unique.
|
||||
func NewUnionMembership(member ...UnionMember) *UnionMembership {
|
||||
return NewDiscriminatedUnionMembership("", member...)
|
||||
}
|
||||
|
||||
// NewDiscriminatedUnionMembership returns a new UnionMembership for the given discriminator field and list of members.
|
||||
// members are provided in the same way as for NewUnionMembership.
|
||||
func NewDiscriminatedUnionMembership(discriminatorFieldName string, members ...UnionMember) *UnionMembership {
|
||||
return &UnionMembership{
|
||||
discriminatorName: discriminatorFieldName,
|
||||
members: members,
|
||||
}
|
||||
}
|
||||
|
||||
// allFields returns a string listing all the field names of the member of a union for use in error reporting.
|
||||
func (u UnionMembership) allFields() []string {
|
||||
memberNames := make([]string, 0, len(u.members))
|
||||
for _, f := range u.members {
|
||||
memberNames = append(memberNames, fmt.Sprintf("`%s`", f.fieldName))
|
||||
}
|
||||
return memberNames
|
||||
}
|
||||
|
||||
func unionValidate[T any](op operation.Operation, fldPath *field.Path,
|
||||
obj, oldObj T, union *UnionMembership, options UnionValidationOptions, isSetFns ...ExtractorFn[T, bool],
|
||||
) field.ErrorList {
|
||||
if len(union.members) != len(isSetFns) {
|
||||
return field.ErrorList{
|
||||
field.InternalError(fldPath,
|
||||
fmt.Errorf("number of extractors (%d) does not match number of union members (%d)",
|
||||
len(isSetFns), len(union.members))),
|
||||
}
|
||||
}
|
||||
|
||||
var specifiedFields []string
|
||||
var changed bool
|
||||
for i, fieldIsSet := range isSetFns {
|
||||
newIsSet := fieldIsSet(obj)
|
||||
if op.Type == operation.Update && !changed {
|
||||
oldIsSet := fieldIsSet(oldObj)
|
||||
changed = changed || newIsSet != oldIsSet
|
||||
}
|
||||
if newIsSet {
|
||||
specifiedFields = append(specifiedFields, union.members[i].fieldName)
|
||||
}
|
||||
}
|
||||
|
||||
// If the union membership is unchanged, we don't need to re-validate.
|
||||
if op.Type == operation.Update && !changed {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs field.ErrorList
|
||||
|
||||
if len(specifiedFields) > 1 && options.ErrorForMultiple != nil {
|
||||
errs = append(errs, options.ErrorForMultiple(fldPath, specifiedFields, union.allFields()))
|
||||
}
|
||||
|
||||
if len(specifiedFields) == 0 && options.ErrorForEmpty != nil {
|
||||
errs = append(errs, options.ErrorForEmpty(fldPath, union.allFields()))
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
160
vendor/k8s.io/apimachinery/pkg/api/validate/update.go
generated
vendored
Normal file
160
vendor/k8s.io/apimachinery/pkg/api/validate/update.go
generated
vendored
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
package validate
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// UpdateConstraint represents a constraint on update operations
|
||||
type UpdateConstraint int
|
||||
|
||||
const (
|
||||
// NoSet prevents unset->set transitions
|
||||
NoSet UpdateConstraint = iota
|
||||
// NoUnset prevents set->unset transitions
|
||||
NoUnset
|
||||
// NoModify prevents value changes but allows set/unset transitions
|
||||
NoModify
|
||||
)
|
||||
|
||||
// UpdateValueByCompare verifies update constraints for comparable value types.
|
||||
func UpdateValueByCompare[T comparable](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
|
||||
if op.Type != operation.Update {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs field.ErrorList
|
||||
var zero T
|
||||
|
||||
for _, constraint := range constraints {
|
||||
switch constraint {
|
||||
case NoSet:
|
||||
if *oldValue == zero && *value != zero {
|
||||
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update"))
|
||||
}
|
||||
case NoUnset:
|
||||
if *oldValue != zero && *value == zero {
|
||||
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update"))
|
||||
}
|
||||
case NoModify:
|
||||
// Rely on validation ratcheting to detect that the value has changed.
|
||||
// This check only verifies that the field was set in both the old and
|
||||
// new objects, confirming it was a modification, not a set/unset.
|
||||
if *oldValue != zero && *value != zero {
|
||||
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// UpdatePointer verifies update constraints for pointer types.
|
||||
func UpdatePointer[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
|
||||
if op.Type != operation.Update {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs field.ErrorList
|
||||
|
||||
for _, constraint := range constraints {
|
||||
switch constraint {
|
||||
case NoSet:
|
||||
if oldValue == nil && value != nil {
|
||||
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update"))
|
||||
}
|
||||
case NoUnset:
|
||||
if oldValue != nil && value == nil {
|
||||
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update"))
|
||||
}
|
||||
case NoModify:
|
||||
// Rely on validation ratcheting to detect that the value has changed.
|
||||
// This check only verifies that the field was non-nil in both the old
|
||||
// and new objects, confirming it was a modification, not a set/unset.
|
||||
if oldValue != nil && value != nil {
|
||||
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// UpdateValueByReflect verifies update constraints for non-comparable value types using reflection.
|
||||
func UpdateValueByReflect[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
|
||||
if op.Type != operation.Update {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs field.ErrorList
|
||||
var zero T
|
||||
valueIsZero := equality.Semantic.DeepEqual(*value, zero)
|
||||
oldValueIsZero := equality.Semantic.DeepEqual(*oldValue, zero)
|
||||
|
||||
for _, constraint := range constraints {
|
||||
switch constraint {
|
||||
case NoSet:
|
||||
if oldValueIsZero && !valueIsZero {
|
||||
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be set once created").WithOrigin("update"))
|
||||
}
|
||||
case NoUnset:
|
||||
if !oldValueIsZero && valueIsZero {
|
||||
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be cleared once set").WithOrigin("update"))
|
||||
}
|
||||
case NoModify:
|
||||
// Rely on validation ratcheting to detect that the value has changed.
|
||||
// This check only verifies that the field was set in both the old and
|
||||
// new objects, confirming it was a modification, not a set/unset.
|
||||
if !oldValueIsZero && !valueIsZero {
|
||||
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// UpdateStruct verifies update constraints for non-pointer struct types.
|
||||
// Non-pointer structs are always considered "set" and never "unset".
|
||||
func UpdateStruct[T any](_ context.Context, op operation.Operation, fldPath *field.Path, value, oldValue *T, constraints ...UpdateConstraint) field.ErrorList {
|
||||
if op.Type != operation.Update {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errs field.ErrorList
|
||||
|
||||
for _, constraint := range constraints {
|
||||
switch constraint {
|
||||
case NoSet, NoUnset:
|
||||
// These constraints don't apply to non-pointer structs
|
||||
// as they can't be unset. This should be caught at generation time.
|
||||
continue
|
||||
case NoModify:
|
||||
// Non-pointer structs are always considered "set". Therefore, any
|
||||
// change detected by validation ratcheting is a modification.
|
||||
// The deep equality check is redundant and has been removed.
|
||||
errs = append(errs, field.Invalid(fldPath, nil, "field cannot be modified once set").WithOrigin("update"))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
59
vendor/k8s.io/apimachinery/pkg/api/validate/zeroorone.go
generated
vendored
Normal file
59
vendor/k8s.io/apimachinery/pkg/api/validate/zeroorone.go
generated
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
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"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
// ZeroOrOneOfUnion verifies that at most one member of a union is specified.
|
||||
//
|
||||
// ZeroOrOneOfMembership must define all the members of the union.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// var ZeroOrOneOfMembershipForABC = validate.NewUnionMembership(
|
||||
// validate.NewUnionMember("a"),
|
||||
// validate.NewUnionMember("b"),
|
||||
// validate.NewUnionMember("c"),
|
||||
// )
|
||||
// func ValidateABC(ctx context.Context, op operation.Operation, fldPath *field.Path, in *ABC) (errs field.ErrorList) {
|
||||
// errs = append(errs, validate.ZeroOrOneOfUnion(ctx, op, fldPath, in, oldIn,
|
||||
// ZeroOrOneOfMembershipForABC,
|
||||
// func(in *ABC) bool { return in.A != nil },
|
||||
// func(in *ABC) bool { return in.B != ""},
|
||||
// func(in *ABC) bool { return in.C != 0 },
|
||||
// )...)
|
||||
// return errs
|
||||
// }
|
||||
func ZeroOrOneOfUnion[T any](_ context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj T, union *UnionMembership, isSetFns ...ExtractorFn[T, bool]) field.ErrorList {
|
||||
options := UnionValidationOptions{
|
||||
ErrorForEmpty: nil,
|
||||
ErrorForMultiple: func(fldPath *field.Path, specifiedFields []string, allFields []string) *field.Error {
|
||||
return field.Invalid(fldPath, fmt.Sprintf("{%s}", strings.Join(specifiedFields, ", ")),
|
||||
fmt.Sprintf("must specify at most one of: %s", strings.Join(allFields, ", "))).WithOrigin("zeroOrOneOf")
|
||||
},
|
||||
}
|
||||
|
||||
errs := unionValidate(op, fldPath, obj, oldObj, union, options, isSetFns...)
|
||||
return errs
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue