Add continuation to conversion routines

This commit is contained in:
Daniel Smith 2014-09-05 16:02:39 -07:00
parent 2978c9923e
commit 2ba6503511
3 changed files with 52 additions and 31 deletions

View File

@ -19,22 +19,20 @@ package v1beta1
import (
// Alias this so it can be easily changed when we cut the next version.
newer "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
)
func init() {
// Shortcut for sub-conversions. TODO: This should possibly be refactored
// such that this convert function is passed to each conversion func.
Convert := runtime.Convert
runtime.AddConversionFuncs(
// EnvVar's Key is deprecated in favor of Name.
func(in *newer.EnvVar, out *EnvVar) error {
func(in *newer.EnvVar, out *EnvVar, s conversion.Scope) error {
out.Value = in.Value
out.Key = in.Name
out.Name = in.Name
return nil
},
func(in *EnvVar, out *newer.EnvVar) error {
func(in *EnvVar, out *newer.EnvVar, s conversion.Scope) error {
out.Value = in.Value
if in.Name != "" {
out.Name = in.Name
@ -45,7 +43,7 @@ func init() {
},
// Path & MountType are deprecated.
func(in *newer.VolumeMount, out *VolumeMount) error {
func(in *newer.VolumeMount, out *VolumeMount, s conversion.Scope) error {
out.Name = in.Name
out.ReadOnly = in.ReadOnly
out.MountPath = in.MountPath
@ -53,7 +51,7 @@ func init() {
out.MountType = "" // MountType is ignored.
return nil
},
func(in *VolumeMount, out *newer.VolumeMount) error {
func(in *VolumeMount, out *newer.VolumeMount, s conversion.Scope) error {
out.Name = in.Name
out.ReadOnly = in.ReadOnly
if in.MountPath == "" {
@ -65,18 +63,18 @@ func init() {
},
// MinionList.Items had a wrong name in v1beta1
func(in *newer.MinionList, out *MinionList) error {
Convert(&in.JSONBase, &out.JSONBase)
Convert(&in.Items, &out.Items)
func(in *newer.MinionList, out *MinionList, s conversion.Scope) error {
s.Convert(&in.JSONBase, &out.JSONBase, 0)
s.Convert(&in.Items, &out.Items, 0)
out.Minions = out.Items
return nil
},
func(in *MinionList, out *newer.MinionList) error {
Convert(&in.JSONBase, &out.JSONBase)
func(in *MinionList, out *newer.MinionList, s conversion.Scope) error {
s.Convert(&in.JSONBase, &out.JSONBase, 0)
if len(in.Items) == 0 {
Convert(&in.Minions, &out.Items)
s.Convert(&in.Minions, &out.Items, 0)
} else {
Convert(&in.Items, &out.Items)
s.Convert(&in.Items, &out.Items, 0)
}
return nil
},

View File

@ -37,7 +37,7 @@ type Converter struct {
// do the conversion.
funcs map[typePair]reflect.Value
// If true, print helpful debugging info. Quite verbose.
// If non-nil, will be called to print helpful debugging info. Quite verbose.
Debug DebugLogger
}
@ -48,29 +48,43 @@ func NewConverter() *Converter {
}
}
// Scope is passed to conversion funcs to allow them to continue an ongoing conversion.
// If multiple converters exist in the system, Scope will allow you to use the correct one
// from a conversion function--that is, the one your conversion function was called by.
type Scope interface {
// Call Convert to convert sub-objects. Note that if you call it with your own exact
// parameters, you'll run out of stack space before anything useful happens.
Convert(src, dest interface{}, flags FieldMatchingFlags) error
}
// Register registers a conversion func with the Converter. conversionFunc must take
// two parameters, the input and output type. It must take a pointer to each. It must
// return an error.
// 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) error { ... return nil })
// c.Register(func(in *Pod, out *v1beta1.Pod, s Scope) error { ... return nil })
func (c *Converter) Register(conversionFunc interface{}) error {
fv := reflect.ValueOf(conversionFunc)
ft := fv.Type()
if ft.Kind() != reflect.Func {
return fmt.Errorf("expected func, got: %v", ft)
}
if ft.NumIn() != 2 {
return fmt.Errorf("expected two in params, got: %v", ft)
if ft.NumIn() != 3 {
return fmt.Errorf("expected three 'in' params, got: %v", ft)
}
if ft.NumOut() != 1 {
return fmt.Errorf("expected one out param, got: %v", ft)
return fmt.Errorf("expected one 'out' param, got: %v", ft)
}
if ft.In(0).Kind() != reflect.Ptr {
return fmt.Errorf("expected pointer arg for in param 0, got: %v", ft)
return fmt.Errorf("expected pointer arg for 'in' param 0, got: %v", ft)
}
if ft.In(1).Kind() != reflect.Ptr {
return fmt.Errorf("expected pointer arg for in param 1, got: %v", ft)
return fmt.Errorf("expected pointer arg for 'in' param 1, got: %v", ft)
}
scopeType := Scope(c)
if e, a := reflect.TypeOf(&scopeType).Elem(), ft.In(2); e != a {
return fmt.Errorf("expected '%v' arg for 'in' param 2, got '%v' (%v)", e, a, ft)
}
var forErrorType error
// This convolution is necessary, otherwise TypeOf picks up on the fact
@ -138,7 +152,8 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
if c.Debug != nil {
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
}
ret := fv.Call([]reflect.Value{sv.Addr(), dv.Addr()})[0].Interface()
args := []reflect.Value{sv.Addr(), dv.Addr(), reflect.ValueOf(Scope(c))}
ret := fv.Call(args)[0].Interface()
// This convolution is necssary because nil interfaces won't convert
// to errors.
if ret == nil {

View File

@ -27,28 +27,30 @@ import (
func TestConverter_CallsRegisteredFunctions(t *testing.T) {
type A struct {
Foo string
Baz int
}
type B struct {
Bar string
Baz int
}
type C struct{}
c := NewConverter()
err := c.Register(func(in *A, out *B) error {
err := c.Register(func(in *A, out *B, s Scope) error {
out.Bar = in.Foo
return nil
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) error {
err = c.Register(func(in *B, out *A, s Scope) error {
out.Foo = in.Bar
return nil
return s.Convert(&in.Baz, &out.Baz, 0)
})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
x := A{"hello, intrepid test reader!"}
x := A{"hello, intrepid test reader!", 3}
y := B{}
err = c.Convert(&x, &y, 0)
@ -58,8 +60,11 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
if e, a := x.Foo, y.Bar; e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := x.Baz, y.Baz; e != a {
t.Errorf("expected %v, got %v", e, a)
}
z := B{"all your test are belong to us"}
z := B{"all your test are belong to us", 42}
w := A{}
err = c.Convert(&z, &w, 0)
@ -69,8 +74,11 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
if e, a := z.Bar, w.Foo; e != a {
t.Errorf("expected %v, got %v", e, a)
}
if e, a := z.Baz, w.Baz; e != a {
t.Errorf("expected %v, got %v", e, a)
}
err = c.Register(func(in *A, out *C) error {
err = c.Register(func(in *A, out *C, s Scope) error {
return fmt.Errorf("C can't store an A, silly")
})
if err != nil {