mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-17 20:00:07 +00:00
Merge pull request #132823 from yongruilin/master_vg_enum
feat(validation-gen): add k8s:enum validators
This commit is contained in:
40
staging/src/k8s.io/apimachinery/pkg/api/validate/enum.go
Normal file
40
staging/src/k8s.io/apimachinery/pkg/api/validate/enum.go
Normal file
@@ -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
|
||||
}
|
||||
107
staging/src/k8s.io/apimachinery/pkg/api/validate/enum_test.go
Normal file
107
staging/src/k8s.io/apimachinery/pkg/api/validate/enum_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// +k8s:validation-gen=TypeMeta
|
||||
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme
|
||||
|
||||
// This is a test package.
|
||||
package enum
|
||||
|
||||
import "k8s.io/code-generator/cmd/validation-gen/testscheme"
|
||||
|
||||
var localSchemeBuilder = testscheme.New()
|
||||
|
||||
type Struct struct {
|
||||
TypeMeta int
|
||||
|
||||
Enum0Field Enum0 `json:"enum0Field"`
|
||||
Enum0PtrField *Enum0 `json:"enum0PtrField"`
|
||||
|
||||
Enum1Field Enum1 `json:"enum1Field"`
|
||||
Enum1PtrField *Enum1 `json:"enum1PtrField"`
|
||||
|
||||
Enum2Field Enum2 `json:"enum2Field"`
|
||||
Enum2PtrField *Enum2 `json:"enum2PtrField"`
|
||||
|
||||
NotEnumField NotEnum `json:"notEnumField"`
|
||||
NotEnumPtrField *NotEnum `json:"notEnumPtrField"`
|
||||
}
|
||||
|
||||
// +k8s:enum
|
||||
type Enum0 string // Note: this enum has no values
|
||||
|
||||
// +k8s:enum
|
||||
type Enum1 string // Note: this enum has 1 value
|
||||
|
||||
const (
|
||||
E1V1 Enum1 = "e1v1"
|
||||
)
|
||||
|
||||
// +k8s:enum
|
||||
type Enum2 string // Note: this enum has 2 values
|
||||
|
||||
const (
|
||||
E2V1 Enum2 = "e2v1"
|
||||
E2V2 Enum2 = "e2v2"
|
||||
)
|
||||
|
||||
// Note: this is not an enum because the const values are of type Enum2, and
|
||||
// because go elides intermediate typedefs (this is modelled as "NotEnum" ->
|
||||
// "string" in the AST).
|
||||
type NotEnum Enum2
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
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 enum
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
st := localSchemeBuilder.Test(t)
|
||||
|
||||
st.Value(&Struct{
|
||||
// All zero vals
|
||||
}).ExpectRegexpsByPath(map[string][]string{
|
||||
"enum0Field": {"Unsupported value: \"\"$"},
|
||||
"enum1Field": {"Unsupported value: \"\": supported values: \"e1v1\""},
|
||||
"enum2Field": {"Unsupported value: \"\": supported values: \"e2v1\", \"e2v2\""},
|
||||
})
|
||||
|
||||
st.Value(&Struct{
|
||||
Enum0Field: "", // no valid value exists
|
||||
Enum0PtrField: ptr.To(Enum0("")), // no valid value exists
|
||||
Enum1Field: E1V1,
|
||||
Enum1PtrField: ptr.To(E1V1),
|
||||
Enum2Field: E2V1,
|
||||
Enum2PtrField: ptr.To(E2V1),
|
||||
NotEnumField: "x",
|
||||
NotEnumPtrField: ptr.To(NotEnum("x")),
|
||||
}).ExpectRegexpsByPath(map[string][]string{
|
||||
"enum0Field": {"Unsupported value: \"\"$"},
|
||||
"enum0PtrField": {"Unsupported value: \"\"$"},
|
||||
})
|
||||
|
||||
st.Value(&Struct{
|
||||
Enum0Field: "x", // no valid value exists
|
||||
Enum0PtrField: ptr.To(Enum0("x")), // no valid value exists
|
||||
Enum1Field: "x",
|
||||
Enum1PtrField: ptr.To(Enum1("x")),
|
||||
Enum2Field: "x",
|
||||
Enum2PtrField: ptr.To(Enum2("x")),
|
||||
NotEnumField: "x",
|
||||
NotEnumPtrField: ptr.To(NotEnum("x")),
|
||||
}).ExpectRegexpsByPath(map[string][]string{
|
||||
"enum0Field": {"Unsupported value: \"x\"$"},
|
||||
"enum0PtrField": {"Unsupported value: \"x\"$"},
|
||||
"enum1Field": {"Unsupported value: \"x\": supported values: \"e1v1\""},
|
||||
"enum1PtrField": {"Unsupported value: \"x\": supported values: \"e1v1\""},
|
||||
"enum2Field": {"Unsupported value: \"x\": supported values: \"e2v1\", \"e2v2\""},
|
||||
"enum2PtrField": {"Unsupported value: \"x\": supported values: \"e2v1\", \"e2v2\""},
|
||||
})
|
||||
}
|
||||
135
staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/zz_generated.validations.go
generated
Normal file
135
staging/src/k8s.io/code-generator/cmd/validation-gen/output_tests/tags/enum/zz_generated.validations.go
generated
Normal file
@@ -0,0 +1,135 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by validation-gen. DO NOT EDIT.
|
||||
|
||||
package enum
|
||||
|
||||
import (
|
||||
context "context"
|
||||
fmt "fmt"
|
||||
|
||||
operation "k8s.io/apimachinery/pkg/api/operation"
|
||||
safe "k8s.io/apimachinery/pkg/api/safe"
|
||||
validate "k8s.io/apimachinery/pkg/api/validate"
|
||||
sets "k8s.io/apimachinery/pkg/util/sets"
|
||||
field "k8s.io/apimachinery/pkg/util/validation/field"
|
||||
testscheme "k8s.io/code-generator/cmd/validation-gen/testscheme"
|
||||
)
|
||||
|
||||
func init() { localSchemeBuilder.Register(RegisterValidations) }
|
||||
|
||||
// RegisterValidations adds validation functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterValidations(scheme *testscheme.Scheme) error {
|
||||
scheme.AddValidationFunc((*Struct)(nil), func(ctx context.Context, op operation.Operation, obj, oldObj interface{}) field.ErrorList {
|
||||
switch op.Request.SubresourcePath() {
|
||||
case "/":
|
||||
return Validate_Struct(ctx, op, nil /* fldPath */, obj.(*Struct), safe.Cast[*Struct](oldObj))
|
||||
}
|
||||
return field.ErrorList{field.InternalError(nil, fmt.Errorf("no validation found for %T, subresource: %v", obj, op.Request.SubresourcePath()))}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
var symbolsForEnum0 = sets.New[Enum0]()
|
||||
|
||||
func Validate_Enum0(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Enum0) (errs field.ErrorList) {
|
||||
// type Enum0
|
||||
if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
|
||||
return nil // no changes
|
||||
}
|
||||
errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnum0)...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
var symbolsForEnum1 = sets.New[Enum1](E1V1)
|
||||
|
||||
func Validate_Enum1(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Enum1) (errs field.ErrorList) {
|
||||
// type Enum1
|
||||
if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
|
||||
return nil // no changes
|
||||
}
|
||||
errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnum1)...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
var symbolsForEnum2 = sets.New[Enum2](E2V1, E2V2)
|
||||
|
||||
func Validate_Enum2(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Enum2) (errs field.ErrorList) {
|
||||
// type Enum2
|
||||
if op.Type == operation.Update && (obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj)) {
|
||||
return nil // no changes
|
||||
}
|
||||
errs = append(errs, validate.Enum(ctx, op, fldPath, obj, oldObj, symbolsForEnum2)...)
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func Validate_Struct(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Struct) (errs field.ErrorList) {
|
||||
// field Struct.TypeMeta has no validation
|
||||
|
||||
// field Struct.Enum0Field
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *Enum0) (errs field.ErrorList) {
|
||||
errs = append(errs, Validate_Enum0(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("enum0Field"), &obj.Enum0Field, safe.Field(oldObj, func(oldObj *Struct) *Enum0 { return &oldObj.Enum0Field }))...)
|
||||
|
||||
// field Struct.Enum0PtrField
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *Enum0) (errs field.ErrorList) {
|
||||
errs = append(errs, Validate_Enum0(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("enum0PtrField"), obj.Enum0PtrField, safe.Field(oldObj, func(oldObj *Struct) *Enum0 { return oldObj.Enum0PtrField }))...)
|
||||
|
||||
// field Struct.Enum1Field
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *Enum1) (errs field.ErrorList) {
|
||||
errs = append(errs, Validate_Enum1(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("enum1Field"), &obj.Enum1Field, safe.Field(oldObj, func(oldObj *Struct) *Enum1 { return &oldObj.Enum1Field }))...)
|
||||
|
||||
// field Struct.Enum1PtrField
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *Enum1) (errs field.ErrorList) {
|
||||
errs = append(errs, Validate_Enum1(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("enum1PtrField"), obj.Enum1PtrField, safe.Field(oldObj, func(oldObj *Struct) *Enum1 { return oldObj.Enum1PtrField }))...)
|
||||
|
||||
// field Struct.Enum2Field
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *Enum2) (errs field.ErrorList) {
|
||||
errs = append(errs, Validate_Enum2(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("enum2Field"), &obj.Enum2Field, safe.Field(oldObj, func(oldObj *Struct) *Enum2 { return &oldObj.Enum2Field }))...)
|
||||
|
||||
// field Struct.Enum2PtrField
|
||||
errs = append(errs,
|
||||
func(fldPath *field.Path, obj, oldObj *Enum2) (errs field.ErrorList) {
|
||||
errs = append(errs, Validate_Enum2(ctx, op, fldPath, obj, oldObj)...)
|
||||
return
|
||||
}(fldPath.Child("enum2PtrField"), obj.Enum2PtrField, safe.Field(oldObj, func(oldObj *Struct) *Enum2 { return oldObj.Enum2PtrField }))...)
|
||||
|
||||
// field Struct.NotEnumField has no validation
|
||||
// field Struct.NotEnumPtrField has no validation
|
||||
return errs
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validators
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/code-generator/cmd/validation-gen/util"
|
||||
"k8s.io/gengo/v2/codetags"
|
||||
"k8s.io/gengo/v2/generator"
|
||||
"k8s.io/gengo/v2/types"
|
||||
)
|
||||
|
||||
const enumTagName = "k8s:enum"
|
||||
|
||||
func init() {
|
||||
RegisterTagValidator(&enumTagValidator{})
|
||||
}
|
||||
|
||||
type enumTagValidator struct {
|
||||
enumContext *enumContext
|
||||
}
|
||||
|
||||
func (etv *enumTagValidator) Init(cfg Config) {
|
||||
etv.enumContext = newEnumContext(cfg.GengoContext)
|
||||
}
|
||||
|
||||
func (enumTagValidator) TagName() string {
|
||||
return enumTagName
|
||||
}
|
||||
|
||||
var enumTagValidScopes = sets.New(ScopeType)
|
||||
|
||||
func (enumTagValidator) ValidScopes() sets.Set[Scope] {
|
||||
return enumTagValidScopes
|
||||
}
|
||||
|
||||
var (
|
||||
enumValidator = types.Name{Package: libValidationPkg, Name: "Enum"}
|
||||
)
|
||||
|
||||
var setsNew = types.Name{Package: "k8s.io/apimachinery/pkg/util/sets", Name: "New"}
|
||||
|
||||
func (etv *enumTagValidator) GetValidations(context Context, _ codetags.Tag) (Validations, error) {
|
||||
// NOTE: typedefs to pointers are not supported, so we should never see a pointer here.
|
||||
if t := util.NativeType(context.Type); t != types.String {
|
||||
return Validations{}, fmt.Errorf("can only be used on string types (%s)", rootTypeString(context.Type, t))
|
||||
}
|
||||
|
||||
var result Validations
|
||||
|
||||
if enum, ok := etv.enumContext.EnumType(context.Type); ok {
|
||||
// TODO: Avoid the "local" here. This was added to to avoid errors caused when the package is an empty string.
|
||||
// The correct package would be the output package but is not known here. This does not show up in generated code.
|
||||
// TODO: Append a consistent hash suffix to avoid generated name conflicts?
|
||||
supportVarName := PrivateVar{Name: "SymbolsFor" + context.Type.Name.Name, Package: "local"}
|
||||
supportVar := Variable(supportVarName, Function(enumTagName, DefaultFlags, setsNew, enum.ValueArgs()...).WithTypeArgs(enum.Name))
|
||||
result.AddVariable(supportVar)
|
||||
fn := Function(enumTagName, DefaultFlags, enumValidator, supportVarName)
|
||||
result.AddFunction(fn)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (etv *enumTagValidator) Docs() TagDoc {
|
||||
return TagDoc{
|
||||
Tag: etv.TagName(),
|
||||
Scopes: etv.ValidScopes().UnsortedList(),
|
||||
Description: "Indicates that a string type is an enum. All const values of this type are considered values in the enum.",
|
||||
}
|
||||
}
|
||||
|
||||
func (et *enumType) ValueArgs() []any {
|
||||
var values []any
|
||||
for _, value := range et.SymbolConstants() {
|
||||
values = append(values, value)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func (et *enumType) SymbolConstants() []Identifier {
|
||||
var values []Identifier
|
||||
for _, value := range et.Values {
|
||||
values = append(values, Identifier(value.Name))
|
||||
}
|
||||
slices.SortFunc(values, func(a, b Identifier) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
// TODO: Everything below this comment is copied from kube-openapi's enum.go.
|
||||
|
||||
type enumValue struct {
|
||||
Name types.Name
|
||||
Value string
|
||||
Comment string
|
||||
}
|
||||
|
||||
type enumType struct {
|
||||
Name types.Name
|
||||
Values []*enumValue
|
||||
}
|
||||
|
||||
// enumMap is a map from the name to the matching enum type.
|
||||
type enumMap map[types.Name]*enumType
|
||||
|
||||
type enumContext struct {
|
||||
enumTypes enumMap
|
||||
}
|
||||
|
||||
func newEnumContext(c *generator.Context) *enumContext {
|
||||
return &enumContext{enumTypes: parseEnums(c)}
|
||||
}
|
||||
|
||||
// EnumType checks and finds the enumType for a given type.
|
||||
// If the given type is a known enum type, returns the enumType, true
|
||||
// Otherwise, returns nil, false
|
||||
func (ec *enumContext) EnumType(t *types.Type) (enum *enumType, isEnum bool) {
|
||||
// if t is a pointer, use its underlying type instead
|
||||
if t.Kind == types.Pointer {
|
||||
t = t.Elem
|
||||
}
|
||||
enum, ok := ec.enumTypes[t.Name]
|
||||
return enum, ok
|
||||
}
|
||||
|
||||
// ValueStrings returns all possible values of the enum type as strings
|
||||
// the results are sorted and quoted as Go literals.
|
||||
func (et *enumType) ValueStrings() []string {
|
||||
var values []string
|
||||
for _, value := range et.Values {
|
||||
// use "%q" format to generate a Go literal of the string const value
|
||||
values = append(values, fmt.Sprintf("%q", value.Value))
|
||||
}
|
||||
sort.Strings(values)
|
||||
return values
|
||||
}
|
||||
|
||||
func parseEnums(c *generator.Context) enumMap {
|
||||
// find all enum types.
|
||||
enumTypes := make(enumMap)
|
||||
for _, p := range c.Universe {
|
||||
for _, t := range p.Types {
|
||||
if isEnumType(t) {
|
||||
if _, ok := enumTypes[t.Name]; !ok {
|
||||
enumTypes[t.Name] = &enumType{
|
||||
Name: t.Name,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find all enum values from constants, and try to match each with its type.
|
||||
for _, p := range c.Universe {
|
||||
for _, c := range p.Constants {
|
||||
enumType := c.Underlying
|
||||
if _, ok := enumTypes[enumType.Name]; ok {
|
||||
value := &enumValue{
|
||||
Name: c.Name,
|
||||
Value: *c.ConstValue,
|
||||
Comment: strings.Join(c.CommentLines, " "),
|
||||
}
|
||||
enumTypes[enumType.Name].addIfNotPresent(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return enumTypes
|
||||
}
|
||||
|
||||
func (et *enumType) addIfNotPresent(value *enumValue) {
|
||||
// If we already have an enum case with the same value, then ignore this new
|
||||
// one. This can happen if an enum aliases one from another package and
|
||||
// re-exports the cases.
|
||||
for i, existing := range et.Values {
|
||||
if existing.Value == value.Value {
|
||||
|
||||
// Take the value of the longer comment (or some other deterministic tie breaker)
|
||||
if len(existing.Comment) < len(value.Comment) || (len(existing.Comment) == len(value.Comment) && existing.Comment > value.Comment) {
|
||||
et.Values[i] = value
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
et.Values = append(et.Values, value)
|
||||
}
|
||||
|
||||
// isEnumType checks if a given type is an enum by the definition
|
||||
// An enum type should be an alias of string and has tag '+enum' in its comment.
|
||||
// Additionally, pass the type of builtin 'string' to check against.
|
||||
func isEnumType(t *types.Type) bool {
|
||||
return t.Kind == types.Alias && t.Underlying == types.String && hasEnumTag(t)
|
||||
}
|
||||
|
||||
func hasEnumTag(t *types.Type) bool {
|
||||
return codetags.Extract("+", t.CommentLines)[enumTagName] != nil
|
||||
}
|
||||
Reference in New Issue
Block a user