mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-17 07:03:31 +00:00
Sketch: a third take on defaulting values
This commit is contained in:
@@ -40,7 +40,7 @@ type DebugLogger interface {
|
||||
type Converter struct {
|
||||
// Map from the conversion pair to a function which can
|
||||
// do the conversion.
|
||||
funcs map[typePair]reflect.Value
|
||||
conversionFuncs map[typePair]reflect.Value
|
||||
|
||||
// This is a map from a source field type and name, to a list of destination
|
||||
// field type and name.
|
||||
@@ -51,20 +51,25 @@ type Converter struct {
|
||||
// source field name and type to look for.
|
||||
structFieldSources map[typeNamePair][]typeNamePair
|
||||
|
||||
// Map from a type to a function which applies defaults.
|
||||
defaultingFuncs map[reflect.Type]reflect.Value
|
||||
|
||||
// If non-nil, will be called to print helpful debugging info. Quite verbose.
|
||||
Debug DebugLogger
|
||||
|
||||
// NameFunc is called to retrieve the name of a type; this name is used for the
|
||||
// nameFunc is called to retrieve the name of a type; this name is used for the
|
||||
// purpose of deciding whether two types match or not (i.e., will we attempt to
|
||||
// do a conversion). The default returns the go type name.
|
||||
NameFunc func(t reflect.Type) string
|
||||
nameFunc func(t reflect.Type) string
|
||||
}
|
||||
|
||||
// NewConverter creates a new Converter object.
|
||||
func NewConverter() *Converter {
|
||||
return &Converter{
|
||||
conversionFuncs: map[typePair]reflect.Value{},
|
||||
defaultingFuncs: map[reflect.Type]reflect.Value{},
|
||||
funcs: map[typePair]reflect.Value{},
|
||||
NameFunc: func(t reflect.Type) string { return t.Name() },
|
||||
nameFunc: func(t reflect.Type) string { return t.Name() },
|
||||
structFieldDests: map[typeNamePair][]typeNamePair{},
|
||||
structFieldSources: map[typeNamePair][]typeNamePair{},
|
||||
}
|
||||
@@ -210,14 +215,18 @@ func (s *scope) error(message string, args ...interface{}) error {
|
||||
return fmt.Errorf(where+message, args...)
|
||||
}
|
||||
|
||||
// Register registers a conversion func with the Converter. conversionFunc must take
|
||||
// three parameters: a pointer to the input type, a pointer to the output type, and
|
||||
// a conversion.Scope (which should be used if recursive conversion calls are desired).
|
||||
// It must return an error.
|
||||
// RegisterConversionFunc registers a conversion func with the
|
||||
// Converter. conversionFunc must take three parameters: a pointer to the input
|
||||
// type, a pointer to the output type, and a conversion.Scope (which should be
|
||||
// used if recursive conversion calls are desired). It must return an error.
|
||||
//
|
||||
// Example:
|
||||
// c.Register(func(in *Pod, out *v1beta1.Pod, s Scope) error { ... return nil })
|
||||
func (c *Converter) Register(conversionFunc interface{}) error {
|
||||
// c.RegisteConversionFuncr(
|
||||
// func(in *Pod, out *v1beta1.Pod, s Scope) error {
|
||||
// // conversion logic...
|
||||
// return nil
|
||||
// })
|
||||
func (c *Converter) RegisterConversionFunc(conversionFunc interface{}) error {
|
||||
fv := reflect.ValueOf(conversionFunc)
|
||||
ft := fv.Type()
|
||||
if ft.Kind() != reflect.Func {
|
||||
@@ -246,7 +255,7 @@ func (c *Converter) Register(conversionFunc interface{}) error {
|
||||
if ft.Out(0) != errorType {
|
||||
return fmt.Errorf("expected error return, got: %v", ft)
|
||||
}
|
||||
c.funcs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
|
||||
c.conversionFuncs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -266,6 +275,33 @@ func (c *Converter) SetStructFieldCopy(srcFieldType interface{}, srcFieldName st
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterDefaultingFunc registers a value-defaulting func with the Converter.
|
||||
// defaultingFunc must take one parameters: a pointer to the input type.
|
||||
//
|
||||
// Example:
|
||||
// c.RegisteDefaultingFuncr(
|
||||
// func(in *v1beta1.Pod) {
|
||||
// // defaulting logic...
|
||||
// })
|
||||
func (c *Converter) RegisterDefaultingFunc(defaultingFunc interface{}) error {
|
||||
fv := reflect.ValueOf(defaultingFunc)
|
||||
ft := fv.Type()
|
||||
if ft.Kind() != reflect.Func {
|
||||
return fmt.Errorf("expected func, got: %v", ft)
|
||||
}
|
||||
if ft.NumIn() != 1 {
|
||||
return fmt.Errorf("expected one 'in' param, got: %v", ft)
|
||||
}
|
||||
if ft.NumOut() != 0 {
|
||||
return fmt.Errorf("expected zero 'out' params, got: %v", ft)
|
||||
}
|
||||
if ft.In(0).Kind() != reflect.Ptr {
|
||||
return fmt.Errorf("expected pointer arg for 'in' param 0, got: %v", ft)
|
||||
}
|
||||
c.defaultingFuncs[ft.In(0).Elem()] = fv
|
||||
return nil
|
||||
}
|
||||
|
||||
// FieldMatchingFlags contains a list of ways in which struct fields could be
|
||||
// copied. These constants may be | combined.
|
||||
type FieldMatchingFlags int
|
||||
@@ -379,7 +415,18 @@ func (c *Converter) callCustom(sv, dv, custom reflect.Value, scope *scope) error
|
||||
// one is registered.
|
||||
func (c *Converter) convert(sv, dv reflect.Value, scope *scope) error {
|
||||
dt, st := dv.Type(), sv.Type()
|
||||
if fv, ok := c.funcs[typePair{st, dt}]; ok {
|
||||
|
||||
// Apply default values.
|
||||
if fv, ok := c.defaultingFuncs[st]; ok {
|
||||
if c.Debug != nil {
|
||||
c.Debug.Logf("Applying defaults for '%v'", st)
|
||||
}
|
||||
args := []reflect.Value{sv.Addr()}
|
||||
fv.Call(args)
|
||||
}
|
||||
|
||||
// Convert sv to dv.
|
||||
if fv, ok := c.conversionFuncs[typePair{st, dt}]; ok {
|
||||
if c.Debug != nil {
|
||||
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
|
||||
}
|
||||
@@ -398,10 +445,10 @@ func (c *Converter) defaultConvert(sv, dv reflect.Value, scope *scope) error {
|
||||
return scope.error("Cannot set dest. (Tried to deep copy something with unexported fields?)")
|
||||
}
|
||||
|
||||
if !scope.flags.IsSet(AllowDifferentFieldTypeNames) && c.NameFunc(dt) != c.NameFunc(st) {
|
||||
if !scope.flags.IsSet(AllowDifferentFieldTypeNames) && c.nameFunc(dt) != c.nameFunc(st) {
|
||||
return scope.error(
|
||||
"type names don't match (%v, %v), and no conversion 'func (%v, %v) error' registered.",
|
||||
c.NameFunc(st), c.NameFunc(dt), st, dt)
|
||||
c.nameFunc(st), c.nameFunc(dt), st, dt)
|
||||
}
|
||||
|
||||
switch st.Kind() {
|
||||
@@ -498,6 +545,7 @@ func toKVValue(v reflect.Value) kvValue {
|
||||
case reflect.Struct:
|
||||
return structAdaptor(v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -118,14 +118,14 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
|
||||
type C struct{}
|
||||
c := NewConverter()
|
||||
c.Debug = t
|
||||
err := c.Register(func(in *A, out *B, s Scope) error {
|
||||
err := c.RegisterConversionFunc(func(in *A, out *B, s Scope) error {
|
||||
out.Bar = in.Foo
|
||||
return s.Convert(&in.Baz, &out.Baz, 0)
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
err = c.Register(func(in *B, out *A, s Scope) error {
|
||||
err = c.RegisterConversionFunc(func(in *B, out *A, s Scope) error {
|
||||
out.Foo = in.Bar
|
||||
return s.Convert(&in.Baz, &out.Baz, 0)
|
||||
})
|
||||
@@ -161,7 +161,7 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
|
||||
t.Errorf("expected %v, got %v", e, a)
|
||||
}
|
||||
|
||||
err = c.Register(func(in *A, out *C, s Scope) error {
|
||||
err = c.RegisterConversionFunc(func(in *A, out *C, s Scope) error {
|
||||
return fmt.Errorf("C can't store an A, silly")
|
||||
})
|
||||
if err != nil {
|
||||
@@ -184,7 +184,7 @@ func TestConverter_fuzz(t *testing.T) {
|
||||
|
||||
f := fuzz.New().NilChance(.5).NumElements(0, 100)
|
||||
c := NewConverter()
|
||||
c.NameFunc = func(t reflect.Type) string {
|
||||
c.nameFunc = func(t reflect.Type) string {
|
||||
// Hide the fact that we don't have separate packages for these things.
|
||||
return map[reflect.Type]string{
|
||||
reflect.TypeOf(TestType1{}): "TestType1",
|
||||
@@ -270,7 +270,7 @@ func TestConverter_tags(t *testing.T) {
|
||||
}
|
||||
c := NewConverter()
|
||||
c.Debug = t
|
||||
err := c.Register(
|
||||
err := c.RegisterConversionFunc(
|
||||
func(in *string, out *string, s Scope) error {
|
||||
if e, a := "foo", s.SrcTag().Get("test"); e != a {
|
||||
t.Errorf("expected %v, got %v", e, a)
|
||||
@@ -296,7 +296,7 @@ func TestConverter_meta(t *testing.T) {
|
||||
c := NewConverter()
|
||||
c.Debug = t
|
||||
checks := 0
|
||||
err := c.Register(
|
||||
err := c.RegisterConversionFunc(
|
||||
func(in *Foo, out *Bar, s Scope) error {
|
||||
if s.Meta() == nil || s.Meta().SrcVersion != "test" || s.Meta().DestVersion != "passes" {
|
||||
t.Errorf("Meta did not get passed!")
|
||||
@@ -309,7 +309,7 @@ func TestConverter_meta(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
err = c.Register(
|
||||
err = c.RegisterConversionFunc(
|
||||
func(in *string, out *string, s Scope) error {
|
||||
if s.Meta() == nil || s.Meta().SrcVersion != "test" || s.Meta().DestVersion != "passes" {
|
||||
t.Errorf("Meta did not get passed a second time!")
|
||||
|
@@ -63,7 +63,7 @@ func NewScheme() *Scheme {
|
||||
InternalVersion: "",
|
||||
MetaFactory: DefaultMetaFactory,
|
||||
}
|
||||
s.converter.NameFunc = s.nameFunc
|
||||
s.converter.nameFunc = s.nameFunc
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ func (s *Scheme) NewObject(versionName, kind string) (interface{}, error) {
|
||||
// add conversion functions for things with changed/removed fields.
|
||||
func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
|
||||
for _, f := range conversionFuncs {
|
||||
if err := s.converter.Register(f); err != nil {
|
||||
if err := s.converter.RegisterConversionFunc(f); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -209,6 +209,29 @@ func (s *Scheme) AddStructFieldConversion(srcFieldType interface{}, srcFieldName
|
||||
return s.converter.SetStructFieldCopy(srcFieldType, srcFieldName, destFieldType, destFieldName)
|
||||
}
|
||||
|
||||
// AddDefaultingFuncs adds functions to the list of default-value functions.
|
||||
// Each of the given functions is responsible for applying default values
|
||||
// when converting an instance of a versioned API object into an internal
|
||||
// API object. These functions do not need to handle sub-objects. We deduce
|
||||
// how to call these functions from the types of their two parameters.
|
||||
//
|
||||
// s.AddDefaultingFuncs(
|
||||
// func(obj *v1beta1.Pod) {
|
||||
// if obj.OptionalField == "" {
|
||||
// obj.OptionalField = "DefaultValue"
|
||||
// }
|
||||
// },
|
||||
// )
|
||||
func (s *Scheme) AddDefaultingFuncs(defaultingFuncs ...interface{}) error {
|
||||
for _, f := range defaultingFuncs {
|
||||
err := s.converter.RegisterDefaultingFunc(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert will attempt to convert in into out. Both must be pointers. For easy
|
||||
// testing of conversion functions. Returns an error if the conversion isn't
|
||||
// possible. You can call this with types that haven't been registered (for example,
|
||||
|
Reference in New Issue
Block a user