From 345641f106bc174b55d40d87bd9a0a8a78f10e6c Mon Sep 17 00:00:00 2001 From: yongruilin Date: Tue, 8 Jul 2025 18:37:42 +0000 Subject: [PATCH] feat(validation-gen): add Enum validator function --- .../apimachinery/pkg/api/validate/enum.go | 40 +++++++ .../pkg/api/validate/enum_test.go | 107 ++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 staging/src/k8s.io/apimachinery/pkg/api/validate/enum.go create mode 100644 staging/src/k8s.io/apimachinery/pkg/api/validate/enum_test.go diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/enum.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/enum.go new file mode 100644 index 00000000000..fc2167a410e --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/enum.go @@ -0,0 +1,40 @@ +/* +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 the specified value is one of the valid symbols. +// This is for string enums only. +func Enum[T ~string](_ context.Context, op operation.Operation, fldPath *field.Path, value, _ *T, symbols sets.Set[T]) field.ErrorList { + if value == nil { + return nil + } + if !symbols.Has(*value) { + symbolList := symbols.UnsortedList() + slices.Sort(symbolList) + return field.ErrorList{field.NotSupported[T](fldPath, *value, symbolList)} + } + return nil +} diff --git a/staging/src/k8s.io/apimachinery/pkg/api/validate/enum_test.go b/staging/src/k8s.io/apimachinery/pkg/api/validate/enum_test.go new file mode 100644 index 00000000000..c392aac7cb6 --- /dev/null +++ b/staging/src/k8s.io/apimachinery/pkg/api/validate/enum_test.go @@ -0,0 +1,107 @@ +/* +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" + "testing" + + "k8s.io/apimachinery/pkg/api/operation" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" +) + +func TestEnum(t *testing.T) { + cases := []struct { + value string + valid sets.Set[string] + err bool + }{{ + value: "a", + valid: sets.New("a", "b", "c"), + err: false, + }, { + value: "x", + valid: sets.New("c", "a", "b"), + err: true, + }} + + for i, tc := range cases { + result := Enum(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &tc.value, nil, tc.valid) + if len(result) > 0 && !tc.err { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err { + t.Errorf("case %d: unexpected success", i) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if want, got := `supported values: "a", "b", "c"`, result[0].Detail; got != want { + t.Errorf("case %d: wrong error, expected: %q, got: %q", i, want, got) + } + } + } +} + +func TestEnumTypedef(t *testing.T) { + type StringType string + const ( + NotStringFoo StringType = "foo" + NotStringBar StringType = "bar" + NotStringQux StringType = "qux" + ) + + cases := []struct { + value StringType + valid sets.Set[StringType] + err bool + }{{ + value: "foo", + valid: sets.New(NotStringFoo, NotStringBar, NotStringQux), + err: false, + }, { + value: "x", + valid: sets.New(NotStringFoo, NotStringBar, NotStringQux), + err: true, + }} + + for i, tc := range cases { + result := Enum(context.Background(), operation.Operation{}, field.NewPath("fldpath"), &tc.value, nil, tc.valid) + if len(result) > 0 && !tc.err { + t.Errorf("case %d: unexpected failure: %v", i, fmtErrs(result)) + continue + } + if len(result) == 0 && tc.err { + t.Errorf("case %d: unexpected success", i) + continue + } + if len(result) > 0 { + if len(result) > 1 { + t.Errorf("case %d: unexepected multi-error: %v", i, fmtErrs(result)) + continue + } + if want, got := `supported values: "bar", "foo", "qux"`, result[0].Detail; got != want { + t.Errorf("case %d: wrong error, expected: %q, got: %q", i, want, got) + } + } + } +}