mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +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.
|
||||
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.
|
||||
func NewConverter() *Converter {
|
||||
return &Converter{
|
||||
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
|
||||
// parameters, you'll run out of stack space before anything useful happens.
|
||||
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
|
||||
@ -82,7 +154,7 @@ func (c *Converter) Register(conversionFunc interface{}) error {
|
||||
if ft.In(1).Kind() != reflect.Ptr {
|
||||
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 {
|
||||
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.
|
||||
// If no conversion func is registered and the default copying mechanism
|
||||
// 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!
|
||||
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)
|
||||
if dv.Kind() != reflect.Ptr {
|
||||
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() {
|
||||
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
|
||||
// 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()
|
||||
if fv, ok := c.funcs[typePair{st, dt}]; ok {
|
||||
if c.Debug != nil {
|
||||
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()
|
||||
// This convolution is necssary because nil interfaces won't convert
|
||||
// to errors.
|
||||
@ -162,7 +244,7 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
|
||||
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)
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
scope.push()
|
||||
defer scope.pop()
|
||||
|
||||
switch dv.Kind() {
|
||||
case reflect.Struct:
|
||||
listType := dt
|
||||
if flags.IsSet(SourceToDest) {
|
||||
if scope.flags.IsSet(SourceToDest) {
|
||||
listType = st
|
||||
}
|
||||
for i := 0; i < listType.NumField(); i++ {
|
||||
f := listType.Field(i)
|
||||
df := dv.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() {
|
||||
switch {
|
||||
case flags.IsSet(IgnoreMissingFields):
|
||||
case scope.flags.IsSet(IgnoreMissingFields):
|
||||
// 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)
|
||||
default:
|
||||
return fmt.Errorf("%v not present in src (%v to %v)", f.Name, st, dt)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := c.convert(sf, df, flags); err != nil {
|
||||
if err := c.convert(sf, df, scope); err != nil {
|
||||
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()))
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -224,7 +319,7 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
|
||||
return nil
|
||||
}
|
||||
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:
|
||||
if sv.IsNil() {
|
||||
// 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))
|
||||
for _, sk := range sv.MapKeys() {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
dv.SetMapIndex(dk, dkv)
|
||||
|
@ -35,6 +35,7 @@ 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 {
|
||||
out.Bar = in.Foo
|
||||
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}
|
||||
y := B{}
|
||||
|
||||
err = c.Convert(&x, &y, 0)
|
||||
err = c.Convert(&x, &y, 0, nil)
|
||||
if err != nil {
|
||||
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}
|
||||
w := A{}
|
||||
|
||||
err = c.Convert(&z, &w, 0)
|
||||
err = c.Convert(&z, &w, 0, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
@ -85,36 +86,43 @@ func TestConverter_CallsRegisteredFunctions(t *testing.T) {
|
||||
t.Fatalf("unexpected error %v", err)
|
||||
}
|
||||
|
||||
err = c.Convert(&A{}, &C{}, 0)
|
||||
err = c.Convert(&A{}, &C{}, 0, nil)
|
||||
if err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
table := []struct {
|
||||
from, to, check interface{}
|
||||
}{
|
||||
{&TestType1{}, newAnonType(), &TestType1{}},
|
||||
{newAnonType(), &TestType1{}, newAnonType()},
|
||||
{&TestType1{}, &ExternalTestType1{}, &TestType1{}},
|
||||
{&ExternalTestType1{}, &TestType1{}, &ExternalTestType1{}},
|
||||
}
|
||||
|
||||
f := fuzz.New().NilChance(.5).NumElements(0, 100)
|
||||
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 j := 0; j < *fuzzIters; j++ {
|
||||
f.Fuzz(item.from)
|
||||
err := c.Convert(item.from, item.to, 0)
|
||||
err := c.Convert(item.from, item.to, 0, nil)
|
||||
if err != nil {
|
||||
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
||||
continue
|
||||
}
|
||||
err = c.Convert(item.to, item.check, 0)
|
||||
err = c.Convert(item.to, item.check, 0, nil)
|
||||
if err != nil {
|
||||
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
||||
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) {
|
||||
type Foo 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)
|
||||
c := NewConverter()
|
||||
c.Debug = t
|
||||
|
||||
for i, item := range table {
|
||||
for j := 0; j < *fuzzIters; j++ {
|
||||
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 {
|
||||
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
|
||||
continue
|
||||
|
@ -58,7 +58,7 @@ func (s *Scheme) Decode(data []byte) (interface{}, error) {
|
||||
if err != nil {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
@ -112,7 +112,7 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.converter.Convert(external, obj, 0)
|
||||
err = s.converter.Convert(external, obj, 0, s.generateConvertMeta(dataVersion, objVersion))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []by
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = s.converter.Convert(obj, objOut, 0)
|
||||
err = s.converter.Convert(obj, objOut, 0, s.generateConvertMeta(objVersion, destVersion))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -51,6 +51,10 @@ type Scheme struct {
|
||||
// is registered for multiple versions, the last one wins.
|
||||
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
|
||||
// default coverting behavior.
|
||||
converter *Converter
|
||||
@ -73,14 +77,22 @@ type Scheme struct {
|
||||
|
||||
// NewScheme manufactures a new scheme.
|
||||
func NewScheme() *Scheme {
|
||||
return &Scheme{
|
||||
s := &Scheme{
|
||||
versionMap: map[string]map[string]reflect.Type{},
|
||||
typeToVersion: map[reflect.Type]string{},
|
||||
typeToKind: map[reflect.Type]string{},
|
||||
converter: NewConverter(),
|
||||
InternalVersion: "",
|
||||
ExternalVersion: "v1",
|
||||
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.
|
||||
@ -104,9 +116,32 @@ func (s *Scheme) AddKnownTypes(version string, types ...interface{}) {
|
||||
}
|
||||
knownTypes[t.Name()] = t
|
||||
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,
|
||||
// or an error if it hasn't been registered.
|
||||
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
|
||||
// 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
|
||||
// s.Convert() inside your conversionFuncs, as long as you don't start a conversion
|
||||
// chain that's infinitely recursive.
|
||||
// Note that, if you need to copy sub-objects that didn't change, you can use the
|
||||
// conversion.Scope object that will be passed to your conversion function.
|
||||
// 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
|
||||
// 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
|
||||
// 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 {
|
||||
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.
|
||||
@ -194,11 +262,12 @@ func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string,
|
||||
return "", "", err
|
||||
}
|
||||
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)
|
||||
} else {
|
||||
return version, t.Name(), nil
|
||||
}
|
||||
return version, kind, nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (s *Scheme) SetVersionAndKind(version, kind string, obj interface{}) error {
|
||||
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
|
||||
|
@ -63,34 +63,28 @@ type TestType2 struct {
|
||||
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||
}
|
||||
|
||||
// We depend on the name of the external and internal types matching. Ordinarily,
|
||||
// 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"`
|
||||
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||
}
|
||||
type TestType1 struct {
|
||||
MyWeirdCustomEmbeddedVersionKindField `json:",inline" yaml:",inline"`
|
||||
A string `yaml:"A,omitempty" json:"A,omitempty"`
|
||||
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||
C int8 `yaml:"C,omitempty" json:"C,omitempty"`
|
||||
D int16 `yaml:"D,omitempty" json:"D,omitempty"`
|
||||
E int32 `yaml:"E,omitempty" json:"E,omitempty"`
|
||||
F int64 `yaml:"F,omitempty" json:"F,omitempty"`
|
||||
G uint `yaml:"G,omitempty" json:"G,omitempty"`
|
||||
H uint8 `yaml:"H,omitempty" json:"H,omitempty"`
|
||||
I uint16 `yaml:"I,omitempty" json:"I,omitempty"`
|
||||
J uint32 `yaml:"J,omitempty" json:"J,omitempty"`
|
||||
K uint64 `yaml:"K,omitempty" json:"K,omitempty"`
|
||||
L bool `yaml:"L,omitempty" json:"L,omitempty"`
|
||||
M map[string]int `yaml:"M,omitempty" json:"M,omitempty"`
|
||||
N map[string]TestType2 `yaml:"N,omitempty" json:"N,omitempty"`
|
||||
O *TestType2 `yaml:"O,omitempty" json:"O,omitempty"`
|
||||
P []TestType2 `yaml:"Q,omitempty" json:"Q,omitempty"`
|
||||
}
|
||||
return &TestType1{}
|
||||
type ExternalTestType2 struct {
|
||||
A string `yaml:"A,omitempty" json:"A,omitempty"`
|
||||
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||
}
|
||||
type ExternalTestType1 struct {
|
||||
MyWeirdCustomEmbeddedVersionKindField `json:",inline" yaml:",inline"`
|
||||
A string `yaml:"A,omitempty" json:"A,omitempty"`
|
||||
B int `yaml:"B,omitempty" json:"B,omitempty"`
|
||||
C int8 `yaml:"C,omitempty" json:"C,omitempty"`
|
||||
D int16 `yaml:"D,omitempty" json:"D,omitempty"`
|
||||
E int32 `yaml:"E,omitempty" json:"E,omitempty"`
|
||||
F int64 `yaml:"F,omitempty" json:"F,omitempty"`
|
||||
G uint `yaml:"G,omitempty" json:"G,omitempty"`
|
||||
H uint8 `yaml:"H,omitempty" json:"H,omitempty"`
|
||||
I uint16 `yaml:"I,omitempty" json:"I,omitempty"`
|
||||
J uint32 `yaml:"J,omitempty" json:"J,omitempty"`
|
||||
K uint64 `yaml:"K,omitempty" json:"K,omitempty"`
|
||||
L bool `yaml:"L,omitempty" json:"L,omitempty"`
|
||||
M map[string]int `yaml:"M,omitempty" json:"M,omitempty"`
|
||||
N map[string]ExternalTestType2 `yaml:"N,omitempty" json:"N,omitempty"`
|
||||
O *ExternalTestType2 `yaml:"O,omitempty" json:"O,omitempty"`
|
||||
P []ExternalTestType2 `yaml:"Q,omitempty" json:"Q,omitempty"`
|
||||
}
|
||||
|
||||
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.
|
||||
func GetTestScheme() *Scheme {
|
||||
s := NewScheme()
|
||||
s.AddKnownTypes("", &TestType1{}, &ExternalInternalSame{})
|
||||
s.AddKnownTypes("v1", externalTypeReturn(), &ExternalInternalSame{})
|
||||
// Ordinarily, we wouldn't add TestType2, but because this is a test and
|
||||
// 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.InternalVersion = ""
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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