mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-09 12:07:47 +00:00
Make needed changes in conversion package to support pluggability
This commit is contained in:
parent
91e9089819
commit
71e547124c
@ -39,12 +39,18 @@ type Converter struct {
|
|||||||
|
|
||||||
// If non-nil, will be called to print helpful debugging info. Quite verbose.
|
// If non-nil, will be called to print helpful debugging info. Quite verbose.
|
||||||
Debug DebugLogger
|
Debug DebugLogger
|
||||||
|
|
||||||
|
// Name 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.
|
||||||
|
Name func(t reflect.Type) string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConverter creates a new Converter object.
|
// NewConverter creates a new Converter object.
|
||||||
func NewConverter() *Converter {
|
func NewConverter() *Converter {
|
||||||
return &Converter{
|
return &Converter{
|
||||||
funcs: map[typePair]reflect.Value{},
|
funcs: map[typePair]reflect.Value{},
|
||||||
|
Name: func(t reflect.Type) string { return t.Name() },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +61,72 @@ type Scope interface {
|
|||||||
// Call Convert to convert sub-objects. Note that if you call it with your own exact
|
// 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.
|
// parameters, you'll run out of stack space before anything useful happens.
|
||||||
Convert(src, dest interface{}, flags FieldMatchingFlags) error
|
Convert(src, dest interface{}, flags FieldMatchingFlags) error
|
||||||
|
|
||||||
|
// SrcTags and DestTags contain the struct tags that src and dest had, respectively.
|
||||||
|
// If the enclosing object was not a struct, then these will contain no tags, of course.
|
||||||
|
SrcTag() reflect.StructTag
|
||||||
|
DestTag() reflect.StructTag
|
||||||
|
|
||||||
|
// Flags returns the flags with which the conversion was started.
|
||||||
|
Flags() FieldMatchingFlags
|
||||||
|
|
||||||
|
// Meta returns any information originally passed to Convert.
|
||||||
|
Meta() map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scope contains information about an ongoing conversion.
|
||||||
|
type scope struct {
|
||||||
|
converter *Converter
|
||||||
|
meta map[string]interface{}
|
||||||
|
flags FieldMatchingFlags
|
||||||
|
srcTagStack []reflect.StructTag
|
||||||
|
destTagStack []reflect.StructTag
|
||||||
|
}
|
||||||
|
|
||||||
|
// push adds a level to the src/dest tag stacks.
|
||||||
|
func (sa *scope) push() {
|
||||||
|
sa.srcTagStack = append(sa.srcTagStack, "")
|
||||||
|
sa.destTagStack = append(sa.destTagStack, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// pop removes a level to the src/dest tag stacks.
|
||||||
|
func (sa *scope) pop() {
|
||||||
|
n := len(sa.srcTagStack)
|
||||||
|
sa.srcTagStack = sa.srcTagStack[:n-1]
|
||||||
|
sa.destTagStack = sa.destTagStack[:n-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sa *scope) setSrcTag(tag reflect.StructTag) {
|
||||||
|
sa.srcTagStack[len(sa.srcTagStack)-1] = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sa *scope) setDestTag(tag reflect.StructTag) {
|
||||||
|
sa.destTagStack[len(sa.destTagStack)-1] = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert continues a conversion.
|
||||||
|
func (sa *scope) Convert(src, dest interface{}, flags FieldMatchingFlags) error {
|
||||||
|
return sa.converter.Convert(src, dest, flags, sa.meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SrcTag returns the tag of the struct containing the current source item, if any.
|
||||||
|
func (sa *scope) SrcTag() reflect.StructTag {
|
||||||
|
return sa.srcTagStack[len(sa.srcTagStack)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// DestTag returns the tag of the struct containing the current dest item, if any.
|
||||||
|
func (sa *scope) DestTag() reflect.StructTag {
|
||||||
|
return sa.destTagStack[len(sa.destTagStack)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flags returns the flags with which the current conversion was started.
|
||||||
|
func (sa *scope) Flags() FieldMatchingFlags {
|
||||||
|
return sa.flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Meta returns the meta object that was originally passed to Convert.
|
||||||
|
func (sa *scope) Meta() map[string]interface{} {
|
||||||
|
return sa.meta
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register registers a conversion func with the Converter. conversionFunc must take
|
// Register registers a conversion func with the Converter. conversionFunc must take
|
||||||
@ -82,7 +154,7 @@ func (c *Converter) Register(conversionFunc interface{}) error {
|
|||||||
if ft.In(1).Kind() != reflect.Ptr {
|
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)
|
scopeType := Scope(nil)
|
||||||
if e, a := reflect.TypeOf(&scopeType).Elem(), ft.In(2); e != a {
|
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)
|
return fmt.Errorf("expected '%v' arg for 'in' param 2, got '%v' (%v)", e, a, ft)
|
||||||
}
|
}
|
||||||
@ -127,8 +199,12 @@ func (f FieldMatchingFlags) IsSet(flag FieldMatchingFlags) bool {
|
|||||||
// Convert will translate src to dest if it knows how. Both must be pointers.
|
// Convert will translate src to dest if it knows how. Both must be pointers.
|
||||||
// If no conversion func is registered and the default copying mechanism
|
// If no conversion func is registered and the default copying mechanism
|
||||||
// doesn't work on this type pair, an error will be returned.
|
// doesn't work on this type pair, an error will be returned.
|
||||||
|
// Read the comments on the various FieldMatchingFlags constants to understand
|
||||||
|
// what the 'flags' parameter does.
|
||||||
|
// 'meta' is given to allow you to pass information to conversion functions,
|
||||||
|
// it is not used by Convert other than storing it in the scope.
|
||||||
// Not safe for objects with cyclic references!
|
// Not safe for objects with cyclic references!
|
||||||
func (c *Converter) Convert(src, dest interface{}, flags FieldMatchingFlags) error {
|
func (c *Converter) Convert(src, dest interface{}, flags FieldMatchingFlags, meta map[string]interface{}) error {
|
||||||
dv, sv := reflect.ValueOf(dest), reflect.ValueOf(src)
|
dv, sv := reflect.ValueOf(dest), reflect.ValueOf(src)
|
||||||
if dv.Kind() != reflect.Ptr {
|
if dv.Kind() != reflect.Ptr {
|
||||||
return fmt.Errorf("Need pointer, but got %#v", dest)
|
return fmt.Errorf("Need pointer, but got %#v", dest)
|
||||||
@ -141,18 +217,24 @@ func (c *Converter) Convert(src, dest interface{}, flags FieldMatchingFlags) err
|
|||||||
if !dv.CanAddr() {
|
if !dv.CanAddr() {
|
||||||
return fmt.Errorf("Can't write to dest")
|
return fmt.Errorf("Can't write to dest")
|
||||||
}
|
}
|
||||||
return c.convert(sv, dv, flags)
|
s := &scope{
|
||||||
|
converter: c,
|
||||||
|
flags: flags,
|
||||||
|
meta: meta,
|
||||||
|
}
|
||||||
|
s.push() // Easy way to make SrcTag and DestTag never fail
|
||||||
|
return c.convert(sv, dv, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert recursively copies sv into dv, calling an appropriate conversion function if
|
// convert recursively copies sv into dv, calling an appropriate conversion function if
|
||||||
// one is registered.
|
// one is registered.
|
||||||
func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) error {
|
func (c *Converter) convert(sv, dv reflect.Value, scope *scope) error {
|
||||||
dt, st := dv.Type(), sv.Type()
|
dt, st := dv.Type(), sv.Type()
|
||||||
if fv, ok := c.funcs[typePair{st, dt}]; ok {
|
if fv, ok := c.funcs[typePair{st, dt}]; ok {
|
||||||
if c.Debug != nil {
|
if c.Debug != nil {
|
||||||
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
|
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
|
||||||
}
|
}
|
||||||
args := []reflect.Value{sv.Addr(), dv.Addr(), reflect.ValueOf(Scope(c))}
|
args := []reflect.Value{sv.Addr(), dv.Addr(), reflect.ValueOf(scope)}
|
||||||
ret := fv.Call(args)[0].Interface()
|
ret := fv.Call(args)[0].Interface()
|
||||||
// This convolution is necssary because nil interfaces won't convert
|
// This convolution is necssary because nil interfaces won't convert
|
||||||
// to errors.
|
// to errors.
|
||||||
@ -162,7 +244,7 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
|
|||||||
return ret.(error)
|
return ret.(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !flags.IsSet(AllowDifferentFieldTypeNames) && dt.Name() != st.Name() {
|
if !scope.flags.IsSet(AllowDifferentFieldTypeNames) && c.Name(dt) != c.Name(st) {
|
||||||
return fmt.Errorf("Can't convert %v to %v because type names don't match.", st, dt)
|
return fmt.Errorf("Can't convert %v to %v because type names don't match.", st, dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,28 +262,41 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
|
|||||||
c.Debug.Logf("Trying to convert '%v' to '%v'", st, dt)
|
c.Debug.Logf("Trying to convert '%v' to '%v'", st, dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scope.push()
|
||||||
|
defer scope.pop()
|
||||||
|
|
||||||
switch dv.Kind() {
|
switch dv.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
listType := dt
|
listType := dt
|
||||||
if flags.IsSet(SourceToDest) {
|
if scope.flags.IsSet(SourceToDest) {
|
||||||
listType = st
|
listType = st
|
||||||
}
|
}
|
||||||
for i := 0; i < listType.NumField(); i++ {
|
for i := 0; i < listType.NumField(); i++ {
|
||||||
f := listType.Field(i)
|
f := listType.Field(i)
|
||||||
df := dv.FieldByName(f.Name)
|
df := dv.FieldByName(f.Name)
|
||||||
sf := sv.FieldByName(f.Name)
|
sf := sv.FieldByName(f.Name)
|
||||||
|
if sf.IsValid() {
|
||||||
|
// No need to check error, since we know it's valid.
|
||||||
|
field, _ := st.FieldByName(f.Name)
|
||||||
|
scope.setSrcTag(field.Tag)
|
||||||
|
}
|
||||||
|
if df.IsValid() {
|
||||||
|
field, _ := dt.FieldByName(f.Name)
|
||||||
|
scope.setDestTag(field.Tag)
|
||||||
|
}
|
||||||
|
// TODO: set top level of scope.src/destTagStack with these field tags here.
|
||||||
if !df.IsValid() || !sf.IsValid() {
|
if !df.IsValid() || !sf.IsValid() {
|
||||||
switch {
|
switch {
|
||||||
case flags.IsSet(IgnoreMissingFields):
|
case scope.flags.IsSet(IgnoreMissingFields):
|
||||||
// No error.
|
// No error.
|
||||||
case flags.IsSet(SourceToDest):
|
case scope.flags.IsSet(SourceToDest):
|
||||||
return fmt.Errorf("%v not present in dest (%v to %v)", f.Name, st, dt)
|
return fmt.Errorf("%v not present in dest (%v to %v)", f.Name, st, dt)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("%v not present in src (%v to %v)", f.Name, st, dt)
|
return fmt.Errorf("%v not present in src (%v to %v)", f.Name, st, dt)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if err := c.convert(sf, df, flags); err != nil {
|
if err := c.convert(sf, df, scope); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,7 +308,7 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
|
|||||||
}
|
}
|
||||||
dv.Set(reflect.MakeSlice(dt, sv.Len(), sv.Cap()))
|
dv.Set(reflect.MakeSlice(dt, sv.Len(), sv.Cap()))
|
||||||
for i := 0; i < sv.Len(); i++ {
|
for i := 0; i < sv.Len(); i++ {
|
||||||
if err := c.convert(sv.Index(i), dv.Index(i), flags); err != nil {
|
if err := c.convert(sv.Index(i), dv.Index(i), scope); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,7 +319,7 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
dv.Set(reflect.New(dt.Elem()))
|
dv.Set(reflect.New(dt.Elem()))
|
||||||
return c.convert(sv.Elem(), dv.Elem(), flags)
|
return c.convert(sv.Elem(), dv.Elem(), scope)
|
||||||
case reflect.Map:
|
case reflect.Map:
|
||||||
if sv.IsNil() {
|
if sv.IsNil() {
|
||||||
// Don't copy a nil ptr!
|
// Don't copy a nil ptr!
|
||||||
@ -234,11 +329,11 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
|
|||||||
dv.Set(reflect.MakeMap(dt))
|
dv.Set(reflect.MakeMap(dt))
|
||||||
for _, sk := range sv.MapKeys() {
|
for _, sk := range sv.MapKeys() {
|
||||||
dk := reflect.New(dt.Key()).Elem()
|
dk := reflect.New(dt.Key()).Elem()
|
||||||
if err := c.convert(sk, dk, flags); err != nil {
|
if err := c.convert(sk, dk, scope); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dkv := reflect.New(dt.Elem()).Elem()
|
dkv := reflect.New(dt.Elem()).Elem()
|
||||||
if err := c.convert(sv.MapIndex(sk), dkv, flags); err != nil {
|
if err := c.convert(sv.MapIndex(sk), dkv, scope); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
dv.SetMapIndex(dk, dkv)
|
dv.SetMapIndex(dk, dkv)
|
||||||
|
@ -35,6 +35,7 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
type C struct{}
|
type C struct{}
|
||||||
c := NewConverter()
|
c := NewConverter()
|
||||||
|
c.Debug = t
|
||||||
err := c.Register(func(in *A, out *B, s Scope) error {
|
err := c.Register(func(in *A, out *B, s Scope) error {
|
||||||
out.Bar = in.Foo
|
out.Bar = in.Foo
|
||||||
return s.Convert(&in.Baz, &out.Baz, 0)
|
return s.Convert(&in.Baz, &out.Baz, 0)
|
||||||
@ -53,7 +54,7 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
|
|||||||
x := A{"hello, intrepid test reader!", 3}
|
x := A{"hello, intrepid test reader!", 3}
|
||||||
y := B{}
|
y := B{}
|
||||||
|
|
||||||
err = c.Convert(&x, &y, 0)
|
err = c.Convert(&x, &y, 0, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error %v", err)
|
t.Fatalf("unexpected error %v", err)
|
||||||
}
|
}
|
||||||
@ -67,7 +68,7 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
|
|||||||
z := B{"all your test are belong to us", 42}
|
z := B{"all your test are belong to us", 42}
|
||||||
w := A{}
|
w := A{}
|
||||||
|
|
||||||
err = c.Convert(&z, &w, 0)
|
err = c.Convert(&z, &w, 0, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error %v", err)
|
t.Fatalf("unexpected error %v", err)
|
||||||
}
|
}
|
||||||
@ -85,36 +86,43 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
|
|||||||
t.Fatalf("unexpected error %v", err)
|
t.Fatalf("unexpected error %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.Convert(&A{}, &C{}, 0)
|
err = c.Convert(&A{}, &C{}, 0, nil)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("unexpected non-error")
|
t.Errorf("unexpected non-error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConverter_fuzz(t *testing.T) {
|
func TestConverter_fuzz(t *testing.T) {
|
||||||
newAnonType := func() interface{} {
|
|
||||||
return reflect.New(reflect.TypeOf(externalTypeReturn()).Elem()).Interface()
|
|
||||||
}
|
|
||||||
// Use the same types from the scheme test.
|
// Use the same types from the scheme test.
|
||||||
table := []struct {
|
table := []struct {
|
||||||
from, to, check interface{}
|
from, to, check interface{}
|
||||||
}{
|
}{
|
||||||
{&TestType1{}, newAnonType(), &TestType1{}},
|
{&TestType1{}, &ExternalTestType1{}, &TestType1{}},
|
||||||
{newAnonType(), &TestType1{}, newAnonType()},
|
{&ExternalTestType1{}, &TestType1{}, &ExternalTestType1{}},
|
||||||
}
|
}
|
||||||
|
|
||||||
f := fuzz.New().NilChance(.5).NumElements(0, 100)
|
f := fuzz.New().NilChance(.5).NumElements(0, 100)
|
||||||
c := NewConverter()
|
c := NewConverter()
|
||||||
|
c.Name = 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",
|
||||||
|
reflect.TypeOf(ExternalTestType1{}): "TestType1",
|
||||||
|
reflect.TypeOf(TestType2{}): "TestType2",
|
||||||
|
reflect.TypeOf(ExternalTestType2{}): "TestType2",
|
||||||
|
}[t]
|
||||||
|
}
|
||||||
|
c.Debug = t
|
||||||
|
|
||||||
for i, item := range table {
|
for i, item := range table {
|
||||||
for j := 0; j < *fuzzIters; j++ {
|
for j := 0; j < *fuzzIters; j++ {
|
||||||
f.Fuzz(item.from)
|
f.Fuzz(item.from)
|
||||||
err := c.Convert(item.from, item.to, 0)
|
err := c.Convert(item.from, item.to, 0, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = c.Convert(item.to, item.check, 0)
|
err = c.Convert(item.to, item.check, 0, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
||||||
continue
|
continue
|
||||||
@ -126,6 +134,72 @@ func TestConverter_fuzz(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConverter_tags(t *testing.T) {
|
||||||
|
type Foo struct {
|
||||||
|
A string `test:"foo"`
|
||||||
|
}
|
||||||
|
type Bar struct {
|
||||||
|
A string `test:"bar"`
|
||||||
|
}
|
||||||
|
c := NewConverter()
|
||||||
|
c.Debug = t
|
||||||
|
err := c.Register(
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if e, a := "bar", s.DestTag().Get("test"); e != a {
|
||||||
|
t.Errorf("expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
c.Convert(&Foo{}, &Bar{}, 0, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConverter_meta(t *testing.T) {
|
||||||
|
type Foo struct{ A string }
|
||||||
|
type Bar struct{ A string }
|
||||||
|
c := NewConverter()
|
||||||
|
c.Debug = t
|
||||||
|
checks := 0
|
||||||
|
err := c.Register(
|
||||||
|
func(in *Foo, out *Bar, s Scope) error {
|
||||||
|
if s.Meta()["test"] != "passes" {
|
||||||
|
t.Errorf("Meta did not get passed!")
|
||||||
|
}
|
||||||
|
checks++
|
||||||
|
s.Convert(&in.A, &out.A, 0)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
err = c.Register(
|
||||||
|
func(in *string, out *string, s Scope) error {
|
||||||
|
if s.Meta()["test"] != "passes" {
|
||||||
|
t.Errorf("Meta did not get passed a second time!")
|
||||||
|
}
|
||||||
|
checks++
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
err = c.Convert(&Foo{}, &Bar{}, 0, map[string]interface{}{"test": "passes"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if checks != 2 {
|
||||||
|
t.Errorf("Registered functions did not get called.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestConverter_flags(t *testing.T) {
|
func TestConverter_flags(t *testing.T) {
|
||||||
type Foo struct{ A string }
|
type Foo struct{ A string }
|
||||||
type Bar struct{ A string }
|
type Bar struct{ A string }
|
||||||
@ -199,11 +273,12 @@ func TestConverter_flags(t *testing.T) {
|
|||||||
}
|
}
|
||||||
f := fuzz.New().NilChance(.5).NumElements(0, 100)
|
f := fuzz.New().NilChance(.5).NumElements(0, 100)
|
||||||
c := NewConverter()
|
c := NewConverter()
|
||||||
|
c.Debug = t
|
||||||
|
|
||||||
for i, item := range table {
|
for i, item := range table {
|
||||||
for j := 0; j < *fuzzIters; j++ {
|
for j := 0; j < *fuzzIters; j++ {
|
||||||
f.Fuzz(item.from)
|
f.Fuzz(item.from)
|
||||||
err := c.Convert(item.from, item.to, item.flags)
|
err := c.Convert(item.from, item.to, item.flags, nil)
|
||||||
if item.shouldSucceed && err != nil {
|
if item.shouldSucceed && err != nil {
|
||||||
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
||||||
continue
|
continue
|
||||||
|
@ -58,7 +58,7 @@ func (s *Scheme) Decode(data []byte) (interface{}, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = s.converter.Convert(obj, objOut, 0)
|
err = s.converter.Convert(obj, objOut, 0, s.generateConvertMeta(version, s.InternalVersion))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = s.converter.Convert(external, obj, 0)
|
err = s.converter.Convert(external, obj, 0, s.generateConvertMeta(dataVersion, objVersion))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []by
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = s.converter.Convert(obj, objOut, 0)
|
err = s.converter.Convert(obj, objOut, 0, s.generateConvertMeta(objVersion, destVersion))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,10 @@ type Scheme struct {
|
|||||||
// is registered for multiple versions, the last one wins.
|
// is registered for multiple versions, the last one wins.
|
||||||
typeToVersion map[reflect.Type]string
|
typeToVersion map[reflect.Type]string
|
||||||
|
|
||||||
|
// typeToKind allows one to figure out the desired "kind" field for a given
|
||||||
|
// go object. Requirements and caveats are the same as typeToVersion.
|
||||||
|
typeToKind map[reflect.Type]string
|
||||||
|
|
||||||
// converter stores all registered conversion functions. It also has
|
// converter stores all registered conversion functions. It also has
|
||||||
// default coverting behavior.
|
// default coverting behavior.
|
||||||
converter *Converter
|
converter *Converter
|
||||||
@ -73,14 +77,22 @@ type Scheme struct {
|
|||||||
|
|
||||||
// NewScheme manufactures a new scheme.
|
// NewScheme manufactures a new scheme.
|
||||||
func NewScheme() *Scheme {
|
func NewScheme() *Scheme {
|
||||||
return &Scheme{
|
s := &Scheme{
|
||||||
versionMap: map[string]map[string]reflect.Type{},
|
versionMap: map[string]map[string]reflect.Type{},
|
||||||
typeToVersion: map[reflect.Type]string{},
|
typeToVersion: map[reflect.Type]string{},
|
||||||
|
typeToKind: map[reflect.Type]string{},
|
||||||
converter: NewConverter(),
|
converter: NewConverter(),
|
||||||
InternalVersion: "",
|
InternalVersion: "",
|
||||||
ExternalVersion: "v1",
|
ExternalVersion: "v1",
|
||||||
MetaInsertionFactory: metaInsertion{},
|
MetaInsertionFactory: metaInsertion{},
|
||||||
}
|
}
|
||||||
|
s.converter.Name = func(t reflect.Type) string {
|
||||||
|
if kind, ok := s.typeToKind[t]; ok {
|
||||||
|
return kind
|
||||||
|
}
|
||||||
|
return t.Name()
|
||||||
|
}
|
||||||
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddKnownTypes registers all types passed in 'types' as being members of version 'version.
|
// AddKnownTypes registers all types passed in 'types' as being members of version 'version.
|
||||||
@ -104,9 +116,32 @@ func (s *Scheme) AddKnownTypes(version string, types ...interface{}) {
|
|||||||
}
|
}
|
||||||
knownTypes[t.Name()] = t
|
knownTypes[t.Name()] = t
|
||||||
s.typeToVersion[t] = version
|
s.typeToVersion[t] = version
|
||||||
|
s.typeToKind[t] = t.Name()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddKnownTypeWithName is like AddKnownTypes, but it lets you specify what this type should
|
||||||
|
// be encoded as. Useful for testing when you don't want to make multiple packages to define
|
||||||
|
// your structs.
|
||||||
|
func (s *Scheme) AddKnownTypeWithName(version, kind string, obj interface{}) {
|
||||||
|
knownTypes, found := s.versionMap[version]
|
||||||
|
if !found {
|
||||||
|
knownTypes = map[string]reflect.Type{}
|
||||||
|
s.versionMap[version] = knownTypes
|
||||||
|
}
|
||||||
|
t := reflect.TypeOf(obj)
|
||||||
|
if t.Kind() != reflect.Ptr {
|
||||||
|
panic("All types must be pointers to structs.")
|
||||||
|
}
|
||||||
|
t = t.Elem()
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
panic("All types must be pointers to structs.")
|
||||||
|
}
|
||||||
|
knownTypes[kind] = t
|
||||||
|
s.typeToVersion[t] = version
|
||||||
|
s.typeToKind[t] = kind
|
||||||
|
}
|
||||||
|
|
||||||
// NewObject returns a new object of the given version and name,
|
// NewObject returns a new object of the given version and name,
|
||||||
// or an error if it hasn't been registered.
|
// or an error if it hasn't been registered.
|
||||||
func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) {
|
func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) {
|
||||||
@ -124,9 +159,23 @@ func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) {
|
|||||||
// sub-objects. We deduce how to call these functions from the types of their two
|
// sub-objects. We deduce how to call these functions from the types of their two
|
||||||
// parameters; see the comment for Converter.Register.
|
// parameters; see the comment for Converter.Register.
|
||||||
//
|
//
|
||||||
// Note that, if you need to copy sub-objects that didn't change, it's safe to call
|
// Note that, if you need to copy sub-objects that didn't change, you can use the
|
||||||
// s.Convert() inside your conversionFuncs, as long as you don't start a conversion
|
// conversion.Scope object that will be passed to your conversion function.
|
||||||
// chain that's infinitely recursive.
|
// Additionally, all conversions started by Scheme will set the "srcVersion" and
|
||||||
|
// "destVersion" keys on the meta object. Example:
|
||||||
|
//
|
||||||
|
// s.AddConversionFuncs(
|
||||||
|
// func(in *InternalObject, out *ExternalObject, scope conversion.Scope) error {
|
||||||
|
// // You can depend on this being set to the source version, e.g., "".
|
||||||
|
// s.Meta()["srcVersion"].(string)
|
||||||
|
// // You can depend on this being set to the destination version,
|
||||||
|
// // e.g., "v1beta1".
|
||||||
|
// s.Meta()["destVersion"].(string)
|
||||||
|
// // Call scope.Convert to copy sub-fields.
|
||||||
|
// s.Convert(&in.SubFieldThatMoved, &out.NewLocation.NewName, 0)
|
||||||
|
// return nil
|
||||||
|
// },
|
||||||
|
// )
|
||||||
//
|
//
|
||||||
// Also note that the default behavior, if you don't add a conversion function, is to
|
// Also note that the default behavior, if you don't add a conversion function, is to
|
||||||
// sanely copy fields that have the same names and same type names. It's OK if the
|
// sanely copy fields that have the same names and same type names. It's OK if the
|
||||||
@ -144,9 +193,28 @@ func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
|
|||||||
|
|
||||||
// Convert will attempt to convert in into out. Both must be pointers. For easy
|
// 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
|
// testing of conversion functions. Returns an error if the conversion isn't
|
||||||
// possible.
|
// possible. You can call this with types that haven't been registered (for example,
|
||||||
|
// a to test conversion of types that are nested within registered types), but in
|
||||||
|
// that case, the conversion.Scope object passed to your conversion functions won't
|
||||||
|
// have "srcVersion" or "destVersion" keys set correctly in Meta().
|
||||||
func (s *Scheme) Convert(in, out interface{}) error {
|
func (s *Scheme) Convert(in, out interface{}) error {
|
||||||
return s.converter.Convert(in, out, 0)
|
inVersion := "unknown"
|
||||||
|
outVersion := "unknown"
|
||||||
|
if v, _, err := s.ObjectVersionAndKind(in); err == nil {
|
||||||
|
inVersion = v
|
||||||
|
}
|
||||||
|
if v, _, err := s.ObjectVersionAndKind(out); err == nil {
|
||||||
|
outVersion = v
|
||||||
|
}
|
||||||
|
return s.converter.Convert(in, out, 0, s.generateConvertMeta(inVersion, outVersion))
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateConvertMeta assembles a map for the meta value we pass to Convert.
|
||||||
|
func (s *Scheme) generateConvertMeta(srcVersion, destVersion string) map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"srcVersion": srcVersion,
|
||||||
|
"destVersion": destVersion,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// metaInsertion provides a default implementation of MetaInsertionFactory.
|
// metaInsertion provides a default implementation of MetaInsertionFactory.
|
||||||
@ -194,11 +262,12 @@ func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string,
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
if version, ok := s.typeToVersion[t]; !ok {
|
version, vOK := s.typeToVersion[t]
|
||||||
|
kind, kOK := s.typeToKind[t]
|
||||||
|
if !vOK || !kOK {
|
||||||
return "", "", fmt.Errorf("Unregistered type: %v", t)
|
return "", "", fmt.Errorf("Unregistered type: %v", t)
|
||||||
} else {
|
|
||||||
return version, t.Name(), nil
|
|
||||||
}
|
}
|
||||||
|
return version, kind, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetVersionAndKind sets the version and kind fields (with help from
|
// SetVersionAndKind sets the version and kind fields (with help from
|
||||||
@ -206,7 +275,7 @@ func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string,
|
|||||||
// must be a pointer.
|
// must be a pointer.
|
||||||
func (s *Scheme) SetVersionAndKind(version, kind string, obj interface{}) error {
|
func (s *Scheme) SetVersionAndKind(version, kind string, obj interface{}) error {
|
||||||
versionAndKind := s.MetaInsertionFactory.Create(version, kind)
|
versionAndKind := s.MetaInsertionFactory.Create(version, kind)
|
||||||
return s.converter.Convert(versionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldTypeNames)
|
return s.converter.Convert(versionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldTypeNames, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// maybeCopy copies obj if it is not a pointer, to get a settable/addressable
|
// maybeCopy copies obj if it is not a pointer, to get a settable/addressable
|
||||||
|
@ -63,15 +63,11 @@ type TestType2 struct {
|
|||||||
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// We depend on the name of the external and internal types matching. Ordinarily,
|
type ExternalTestType2 struct {
|
||||||
// we'd accomplish this with an additional package, but since this is a test, we
|
|
||||||
// can just enclose stuff in a function to simulate that.
|
|
||||||
func externalTypeReturn() interface{} {
|
|
||||||
type TestType2 struct {
|
|
||||||
A string `yaml:"A,omitempty" json:"A,omitempty"`
|
A string `yaml:"A,omitempty" json:"A,omitempty"`
|
||||||
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||||
}
|
}
|
||||||
type TestType1 struct {
|
type ExternalTestType1 struct {
|
||||||
MyWeirdCustomEmbeddedVersionKindField `json:",inline" yaml:",inline"`
|
MyWeirdCustomEmbeddedVersionKindField `json:",inline" yaml:",inline"`
|
||||||
A string `yaml:"A,omitempty" json:"A,omitempty"`
|
A string `yaml:"A,omitempty" json:"A,omitempty"`
|
||||||
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||||
@ -86,11 +82,9 @@ func externalTypeReturn() interface{} {
|
|||||||
K uint64 `yaml:"K,omitempty" json:"K,omitempty"`
|
K uint64 `yaml:"K,omitempty" json:"K,omitempty"`
|
||||||
L bool `yaml:"L,omitempty" json:"L,omitempty"`
|
L bool `yaml:"L,omitempty" json:"L,omitempty"`
|
||||||
M map[string]int `yaml:"M,omitempty" json:"M,omitempty"`
|
M map[string]int `yaml:"M,omitempty" json:"M,omitempty"`
|
||||||
N map[string]TestType2 `yaml:"N,omitempty" json:"N,omitempty"`
|
N map[string]ExternalTestType2 `yaml:"N,omitempty" json:"N,omitempty"`
|
||||||
O *TestType2 `yaml:"O,omitempty" json:"O,omitempty"`
|
O *ExternalTestType2 `yaml:"O,omitempty" json:"O,omitempty"`
|
||||||
P []TestType2 `yaml:"Q,omitempty" json:"Q,omitempty"`
|
P []ExternalTestType2 `yaml:"Q,omitempty" json:"Q,omitempty"`
|
||||||
}
|
|
||||||
return &TestType1{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ExternalInternalSame struct {
|
type ExternalInternalSame struct {
|
||||||
@ -124,8 +118,13 @@ var TestObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 100).Funcs(
|
|||||||
// Returns a new Scheme set up with the test objects.
|
// Returns a new Scheme set up with the test objects.
|
||||||
func GetTestScheme() *Scheme {
|
func GetTestScheme() *Scheme {
|
||||||
s := NewScheme()
|
s := NewScheme()
|
||||||
s.AddKnownTypes("", &TestType1{}, &ExternalInternalSame{})
|
// Ordinarily, we wouldn't add TestType2, but because this is a test and
|
||||||
s.AddKnownTypes("v1", externalTypeReturn(), &ExternalInternalSame{})
|
// both types are from the same package, we need to get it into the system
|
||||||
|
// so that converter will match it with ExternalType2.
|
||||||
|
s.AddKnownTypes("", &TestType1{}, &TestType2{}, &ExternalInternalSame{})
|
||||||
|
s.AddKnownTypes("v1", &ExternalInternalSame{})
|
||||||
|
s.AddKnownTypeWithName("v1", "TestType1", &ExternalTestType1{})
|
||||||
|
s.AddKnownTypeWithName("v1", "TestType2", &ExternalTestType2{})
|
||||||
s.ExternalVersion = "v1"
|
s.ExternalVersion = "v1"
|
||||||
s.InternalVersion = ""
|
s.InternalVersion = ""
|
||||||
s.MetaInsertionFactory = testMetaInsertionFactory{}
|
s.MetaInsertionFactory = testMetaInsertionFactory{}
|
||||||
@ -270,3 +269,145 @@ func TestBadJSONRejection(t *testing.T) {
|
|||||||
t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
|
t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMetaValues(t *testing.T) {
|
||||||
|
type InternalSimple struct {
|
||||||
|
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
||||||
|
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||||
|
TestString string `json:"testString" yaml:"testString"`
|
||||||
|
}
|
||||||
|
type ExternalSimple struct {
|
||||||
|
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
||||||
|
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||||
|
TestString string `json:"testString" yaml:"testString"`
|
||||||
|
}
|
||||||
|
s := NewScheme()
|
||||||
|
s.InternalVersion = ""
|
||||||
|
s.ExternalVersion = "externalVersion"
|
||||||
|
s.AddKnownTypeWithName("", "Simple", &InternalSimple{})
|
||||||
|
s.AddKnownTypeWithName("externalVersion", "Simple", &ExternalSimple{})
|
||||||
|
|
||||||
|
internalToExternalCalls := 0
|
||||||
|
externalToInternalCalls := 0
|
||||||
|
|
||||||
|
// Register functions to verify that scope.Meta() gets set correctly.
|
||||||
|
err := s.AddConversionFuncs(
|
||||||
|
func(in *InternalSimple, out *ExternalSimple, scope Scope) error {
|
||||||
|
if e, a := "", scope.Meta()["srcVersion"].(string); e != a {
|
||||||
|
t.Errorf("Expected '%v', got '%v'", e, a)
|
||||||
|
}
|
||||||
|
if e, a := "externalVersion", scope.Meta()["destVersion"].(string); e != a {
|
||||||
|
t.Errorf("Expected '%v', got '%v'", e, a)
|
||||||
|
}
|
||||||
|
scope.Convert(&in.TestString, &out.TestString, 0)
|
||||||
|
internalToExternalCalls++
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
func(in *ExternalSimple, out *InternalSimple, scope Scope) error {
|
||||||
|
if e, a := "externalVersion", scope.Meta()["srcVersion"].(string); e != a {
|
||||||
|
t.Errorf("Expected '%v', got '%v'", e, a)
|
||||||
|
}
|
||||||
|
if e, a := "", scope.Meta()["destVersion"].(string); e != a {
|
||||||
|
t.Errorf("Expected '%v', got '%v'", e, a)
|
||||||
|
}
|
||||||
|
scope.Convert(&in.TestString, &out.TestString, 0)
|
||||||
|
externalToInternalCalls++
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
simple := &InternalSimple{
|
||||||
|
TestString: "foo",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Encode, Decode, and DecodeInto
|
||||||
|
data, err := s.EncodeToVersion(simple, "externalVersion")
|
||||||
|
obj2, err2 := s.Decode(data)
|
||||||
|
obj3 := &InternalSimple{}
|
||||||
|
err3 := s.DecodeInto(data, obj3)
|
||||||
|
if err != nil || err2 != nil {
|
||||||
|
t.Fatalf("Failure: '%v' '%v' '%v'", err, err2, err3)
|
||||||
|
}
|
||||||
|
if _, ok := obj2.(*InternalSimple); !ok {
|
||||||
|
t.Fatalf("Got wrong type")
|
||||||
|
}
|
||||||
|
if e, a := simple, obj2; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
|
||||||
|
}
|
||||||
|
if e, a := simple, obj3; !reflect.DeepEqual(e, a) {
|
||||||
|
t.Errorf("Expected:\n %#v,\n Got:\n %#v", e, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Convert
|
||||||
|
external := &ExternalSimple{}
|
||||||
|
err = s.Convert(simple, external)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if e, a := simple.TestString, external.TestString; e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode and Convert should each have caused an increment.
|
||||||
|
if e, a := 2, internalToExternalCalls; e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
// Decode and DecodeInto should each have caused an increment.
|
||||||
|
if e, a := 2, externalToInternalCalls; e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMetaValuesUnregisteredConvert(t *testing.T) {
|
||||||
|
type InternalSimple struct {
|
||||||
|
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
||||||
|
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||||
|
TestString string `json:"testString" yaml:"testString"`
|
||||||
|
}
|
||||||
|
type ExternalSimple struct {
|
||||||
|
Version string `json:"version,omitempty" yaml:"version,omitempty"`
|
||||||
|
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
|
||||||
|
TestString string `json:"testString" yaml:"testString"`
|
||||||
|
}
|
||||||
|
s := NewScheme()
|
||||||
|
s.InternalVersion = ""
|
||||||
|
s.ExternalVersion = "externalVersion"
|
||||||
|
// We deliberately don't register the types.
|
||||||
|
|
||||||
|
internalToExternalCalls := 0
|
||||||
|
|
||||||
|
// Register functions to verify that scope.Meta() gets set correctly.
|
||||||
|
err := s.AddConversionFuncs(
|
||||||
|
func(in *InternalSimple, out *ExternalSimple, scope Scope) error {
|
||||||
|
if e, a := "unknown", scope.Meta()["srcVersion"].(string); e != a {
|
||||||
|
t.Errorf("Expected '%v', got '%v'", e, a)
|
||||||
|
}
|
||||||
|
if e, a := "unknown", scope.Meta()["destVersion"].(string); e != a {
|
||||||
|
t.Errorf("Expected '%v', got '%v'", e, a)
|
||||||
|
}
|
||||||
|
scope.Convert(&in.TestString, &out.TestString, 0)
|
||||||
|
internalToExternalCalls++
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
simple := &InternalSimple{TestString: "foo"}
|
||||||
|
external := &ExternalSimple{}
|
||||||
|
err = s.Convert(simple, external)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if e, a := simple.TestString, external.TestString; e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that our conversion handler got called.
|
||||||
|
if e, a := 1, internalToExternalCalls; e != a {
|
||||||
|
t.Errorf("Expected %v, got %v", e, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user