Merge pull request #1699 from smarterclayton/unify_meta_code

Simplify MetaInsertionFactory down to one implementation
This commit is contained in:
Daniel Smith 2014-10-14 10:43:14 -07:00
commit 4d2f9333d8
8 changed files with 418 additions and 309 deletions

View File

@ -51,7 +51,7 @@ import (
//
func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []byte, err error) {
obj = maybeCopy(obj)
v, _ := enforcePtr(obj) // maybeCopy guarantees a pointer
v, _ := EnforcePtr(obj) // maybeCopy guarantees a pointer
if _, registered := s.typeToVersion[v.Type()]; !registered {
return nil, fmt.Errorf("type %v is not registered and it will be impossible to Decode it, therefore Encode will refuse to encode it.", v.Type())
}

119
pkg/conversion/meta.go Normal file
View File

@ -0,0 +1,119 @@
/*
Copyright 2014 Google Inc. All rights reserved.
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 conversion
import (
"fmt"
"reflect"
"gopkg.in/v1/yaml"
)
// MetaFactory is used to store and retrieve the version and kind
// information for all objects in a scheme.
type MetaFactory interface {
// Update sets the given version and kind onto the object.
Update(version, kind string, obj interface{}) error
// Interpret should return the version and kind of the wire-format of
// the object.
Interpret(data []byte) (version, kind string, err error)
}
// DefaultMetaFactory is a default factory for versioning objects in JSON/YAML. The object
// in memory and in the default JSON serialization will use the "kind" and "apiVersion"
// fields.
var DefaultMetaFactory = SimpleMetaFactory{KindField: "Kind", VersionField: "APIVersion"}
// SimpleMetaFactory provides default methods for retrieving the type and version of objects
// that are identified with an "apiVersion" and "kind" fields in their JSON/YAML
// serialization. It may be parameterized with the names of the fields in memory, or an
// optional list of base structs to search for those fields in memory.
type SimpleMetaFactory struct {
// The name of the API version field in memory of the struct
VersionField string
// The name of the kind field in memory of the struct.
KindField string
// Optional, if set will look in the named inline structs to find the fields to set.
BaseFields []string
}
// Interpret will return the APIVersion and Kind of the JSON/YAML wire-format
// encoding of an object, or an error.
func (SimpleMetaFactory) Interpret(data []byte) (version, kind string, err error) {
findKind := struct {
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
}{}
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err = yaml.Unmarshal(data, &findKind)
if err != nil {
return "", "", fmt.Errorf("couldn't get version/kind: %v", err)
}
return findKind.APIVersion, findKind.Kind, nil
}
func (f SimpleMetaFactory) Update(version, kind string, obj interface{}) error {
return UpdateVersionAndKind(f.BaseFields, f.VersionField, version, f.KindField, kind, obj)
}
// UpdateVersionAndKind uses reflection to find and set the versionField and kindField fields
// on a pointer to a struct to version and kind. Provided as a convenience for others
// implementing MetaFactory. Pass an array to baseFields to check one or more nested structs
// for the named fields. The version field is treated as optional if it is not present in the struct.
func UpdateVersionAndKind(baseFields []string, versionField, version, kindField, kind string, obj interface{}) error {
v, err := EnforcePtr(obj)
if err != nil {
return err
}
t := v.Type()
name := t.Name()
if v.Kind() != reflect.Struct {
return fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), name, v.Interface())
}
for i := range baseFields {
base := v.FieldByName(baseFields[i])
if !base.IsValid() {
continue
}
v = base
}
field := v.FieldByName(kindField)
if !field.IsValid() {
return fmt.Errorf("couldn't find %v field in %#v", kindField, v.Interface())
}
field.SetString(kind)
if field := v.FieldByName(versionField); field.IsValid() {
field.SetString(version)
}
return nil
}
// EnforcePtr ensures that obj is a pointer of some sort. Returns a reflect.Value
// of the dereferenced pointer, ensuring that it is settable/addressable.
// Returns an error if this is not possible.
func EnforcePtr(obj interface{}) (reflect.Value, error) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
return reflect.Value{}, fmt.Errorf("expected pointer, but got %v", v.Type().Name())
}
return v.Elem(), nil
}

244
pkg/conversion/meta_test.go Normal file
View File

@ -0,0 +1,244 @@
/*
Copyright 2014 Google Inc. All rights reserved.
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 conversion
import (
"reflect"
"testing"
)
func TestSimpleMetaFactoryInterpret(t *testing.T) {
factory := SimpleMetaFactory{}
version, kind, err := factory.Interpret([]byte(`{"apiVersion":"1","kind":"object"}`))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if version != "1" || kind != "object" {
t.Errorf("unexpected interpret: %s %s", version, kind)
}
// no kind or version
version, kind, err = factory.Interpret([]byte(`{}`))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if version != "" || kind != "" {
t.Errorf("unexpected interpret: %s %s", version, kind)
}
// unparsable
version, kind, err = factory.Interpret([]byte(`{`))
if err == nil {
t.Errorf("unexpected non-error")
}
}
func TestSimpleMetaFactoryUpdate(t *testing.T) {
factory := SimpleMetaFactory{VersionField: "V", KindField: "K"}
obj := struct {
V string
K string
}{"1", "2"}
// must pass a pointer
if err := factory.Update("test", "other", obj); err == nil {
t.Errorf("unexpected non-error")
}
if obj.V != "1" || obj.K != "2" {
t.Errorf("unexpected update: %v", obj)
}
// updates
if err := factory.Update("test", "other", &obj); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if obj.V != "test" || obj.K != "other" {
t.Errorf("unexpected update: %v", obj)
}
}
func TestSimpleMetaFactoryUpdateStruct(t *testing.T) {
factory := SimpleMetaFactory{BaseFields: []string{"Test"}, VersionField: "V", KindField: "K"}
type Inner struct {
V string
K string
}
obj := struct {
Test Inner
}{Test: Inner{"1", "2"}}
// updates
if err := factory.Update("test", "other", &obj); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if obj.Test.V != "test" || obj.Test.K != "other" {
t.Errorf("unexpected update: %v", obj)
}
}
func TestMetaValues(t *testing.T) {
type InternalSimple struct {
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
TestString string `json:"testString" yaml:"testString"`
}
type ExternalSimple struct {
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
TestString string `json:"testString" yaml:"testString"`
}
s := NewScheme()
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 {
t.Logf("internal -> external")
if e, a := "", scope.Meta().SrcVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
if e, a := "externalVersion", scope.Meta().DestVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
scope.Convert(&in.TestString, &out.TestString, 0)
internalToExternalCalls++
return nil
},
func(in *ExternalSimple, out *InternalSimple, scope Scope) error {
t.Logf("external -> internal")
if e, a := "externalVersion", scope.Meta().SrcVersion; e != a {
t.Errorf("Expected '%v', got '%v'", e, a)
}
if e, a := "", scope.Meta().DestVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
scope.Convert(&in.TestString, &out.TestString, 0)
externalToInternalCalls++
return nil
},
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
simple := &InternalSimple{
TestString: "foo",
}
s.Log(t)
// Test Encode, Decode, and DecodeInto
data, err := s.EncodeToVersion(simple, "externalVersion")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
t.Logf(string(data))
obj2, err := s.Decode(data)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
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)
}
obj3 := &InternalSimple{}
if err := s.DecodeInto(data, obj3); err != nil {
t.Fatalf("unexpected error: %v", err)
}
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.Fatalf("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:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
TestString string `json:"testString" yaml:"testString"`
}
type ExternalSimple struct {
Version string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
TestString string `json:"testString" yaml:"testString"`
}
s := NewScheme()
s.InternalVersion = ""
// 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; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
if e, a := "unknown", scope.Meta().DestVersion; e != a {
t.Fatalf("Expected '%v', got '%v'", e, a)
}
scope.Convert(&in.TestString, &out.TestString, 0)
internalToExternalCalls++
return nil
},
)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
simple := &InternalSimple{TestString: "foo"}
external := &ExternalSimple{}
err = s.Convert(simple, external)
if err != nil {
t.Fatalf("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)
}
}

View File

@ -19,27 +19,8 @@ package conversion
import (
"fmt"
"reflect"
"gopkg.in/v1/yaml"
)
// MetaInsertionFactory is used to create an object to store and retrieve
// the version and kind information for all objects. The default uses the
// keys "version" and "kind" respectively. The object produced by this
// factory is used to clear the version and kind fields in memory, so it
// must match the layout of your actual api structs. (E.g., if you have your
// version and kind field inside an inlined struct, this must produce an
// inlined struct with the same field name.)
type MetaInsertionFactory interface {
// Create should make a new object with two fields.
// This object will be used to encode this metadata along with your
// API objects, so the tags on the fields you use shouldn't conflict.
Create(version, kind string) interface{}
// Interpret should take the same type of object that Create creates.
// It should return the version and kind information from this object.
Interpret(interface{}) (version, kind string)
}
// Scheme defines an entire encoding and decoding scheme.
type Scheme struct {
// versionMap allows one to figure out the go type of an object with
@ -68,19 +49,19 @@ type Scheme struct {
// MetaInsertionFactory is used to create an object to store and retrieve
// the version and kind information for all objects. The default uses the
// keys "version" and "kind" respectively.
MetaInsertionFactory MetaInsertionFactory
// keys "apiVersion" and "kind" respectively.
MetaFactory MetaFactory
}
// NewScheme manufactures a new scheme.
func NewScheme() *Scheme {
s := &Scheme{
versionMap: map[string]map[string]reflect.Type{},
typeToVersion: map[reflect.Type]string{},
typeToKind: map[reflect.Type][]string{},
converter: NewConverter(),
InternalVersion: "",
MetaInsertionFactory: metaInsertion{},
versionMap: map[string]map[string]reflect.Type{},
typeToVersion: map[reflect.Type]string{},
typeToKind: map[reflect.Type][]string{},
converter: NewConverter(),
InternalVersion: "",
MetaFactory: DefaultMetaFactory,
}
s.converter.NameFunc = s.nameFunc
return s
@ -238,47 +219,16 @@ func (s *Scheme) generateConvertMeta(srcVersion, destVersion string) *Meta {
}
}
// metaInsertion provides a default implementation of MetaInsertionFactory.
type metaInsertion struct {
Version string `json:"version,omitempty" yaml:"version,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
}
// Create should make a new object with two fields.
// This object will be used to encode this metadata along with your
// API objects, so the tags on the fields you use shouldn't conflict.
func (metaInsertion) Create(version, kind string) interface{} {
m := metaInsertion{}
m.Version = version
m.Kind = kind
return &m
}
// Interpret should take the same type of object that Create creates.
// It should return the version and kind information from this object.
func (metaInsertion) Interpret(in interface{}) (version, kind string) {
m := in.(*metaInsertion)
return m.Version, m.Kind
}
// DataVersionAndKind will return the APIVersion and Kind of the given wire-format
// enconding of an API Object, or an error.
func (s *Scheme) DataVersionAndKind(data []byte) (version, kind string, err error) {
findKind := s.MetaInsertionFactory.Create("", "")
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err = yaml.Unmarshal(data, findKind)
if err != nil {
return "", "", fmt.Errorf("couldn't get version/kind: %v", err)
}
version, kind = s.MetaInsertionFactory.Interpret(findKind)
return version, kind, nil
return s.MetaFactory.Interpret(data)
}
// ObjectVersionAndKind returns the API version and kind of the go object,
// or an error if it's not a pointer or is unregistered.
func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string, err error) {
v, err := enforcePtr(obj)
v, err := EnforcePtr(obj)
if err != nil {
return "", "", err
}
@ -297,8 +247,7 @@ func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string,
// MetaInsertionFactory). Returns an error if this isn't possible. obj
// 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, nil)
return s.MetaFactory.Update(version, kind, obj)
}
// maybeCopy copies obj if it is not a pointer, to get a settable/addressable
@ -312,14 +261,3 @@ func maybeCopy(obj interface{}) interface{} {
v2.Elem().Set(v)
return v2.Interface()
}
// enforcePtr ensures that obj is a pointer of some sort. Returns a reflect.Value
// of the dereferenced pointer, ensuring that it is settable/addressable.
// Returns an error if this is not possible.
func enforcePtr(obj interface{}) (reflect.Value, error) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
return reflect.Value{}, fmt.Errorf("expected pointer, but got %v", v.Type().Name())
}
return v.Elem(), nil
}

View File

@ -25,7 +25,9 @@ import (
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/google/gofuzz"
"gopkg.in/v1/yaml"
)
var fuzzIters = flag.Int("fuzz_iters", 50, "How many fuzzing iterations to do.")
@ -129,30 +131,28 @@ func GetTestScheme() *Scheme {
s.AddKnownTypeWithName("", "TestType3", &TestType1{})
s.AddKnownTypeWithName("v1", "TestType3", &ExternalTestType1{})
s.InternalVersion = ""
s.MetaInsertionFactory = testMetaInsertionFactory{}
s.MetaFactory = testMetaFactory{}
return s
}
type testMetaInsertionFactory struct {
MyWeirdCustomEmbeddedVersionKindField struct {
type testMetaFactory struct{}
func (testMetaFactory) Interpret(data []byte) (version, kind string, err error) {
findKind := struct {
APIVersion string `json:"myVersionKey,omitempty" yaml:"myVersionKey,omitempty"`
ObjectKind string `json:"myKindKey,omitempty" yaml:"myKindKey,omitempty"`
} `json:",inline" yaml:",inline"`
}{}
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err = yaml.Unmarshal(data, &findKind)
if err != nil {
return "", "", fmt.Errorf("couldn't get version/kind: %v", err)
}
return findKind.APIVersion, findKind.ObjectKind, nil
}
// Create returns a new testMetaInsertionFactory with the version and kind fields set.
func (testMetaInsertionFactory) Create(version, kind string) interface{} {
m := testMetaInsertionFactory{}
m.MyWeirdCustomEmbeddedVersionKindField.APIVersion = version
m.MyWeirdCustomEmbeddedVersionKindField.ObjectKind = kind
return &m
}
// Interpret returns the version and kind information from in, which must be
// a testMetaInsertionFactory pointer object.
func (testMetaInsertionFactory) Interpret(in interface{}) (version, kind string) {
m := in.(*testMetaInsertionFactory)
return m.MyWeirdCustomEmbeddedVersionKindField.APIVersion, m.MyWeirdCustomEmbeddedVersionKindField.ObjectKind
func (testMetaFactory) Update(version, kind string, obj interface{}) error {
return UpdateVersionAndKind(nil, "APIVersion", version, "ObjectKind", kind, obj)
}
func objDiff(a, b interface{}) string {
@ -308,143 +308,3 @@ func TestBadJSONRejectionForSetInternalVersion(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.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; e != a {
t.Errorf("Expected '%v', got '%v'", e, a)
}
if e, a := "externalVersion", scope.Meta().DestVersion; 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; e != a {
t.Errorf("Expected '%v', got '%v'", e, a)
}
if e, a := "", scope.Meta().DestVersion; 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 = ""
// 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; e != a {
t.Errorf("Expected '%v', got '%v'", e, a)
}
if e, a := "unknown", scope.Meta().DestVersion; 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)
}
}

View File

@ -17,11 +17,9 @@ limitations under the License.
package runtime
import (
"fmt"
"reflect"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
"gopkg.in/v1/yaml"
)
// Scheme defines methods for serializing and deserializing API objects. It
@ -145,7 +143,7 @@ func (self *Scheme) rawExtensionToEmbeddedObject(in *RawExtension, out *Embedded
func NewScheme() *Scheme {
s := &Scheme{conversion.NewScheme()}
s.raw.InternalVersion = ""
s.raw.MetaInsertionFactory = metaInsertion{}
s.raw.MetaFactory = conversion.SimpleMetaFactory{BaseFields: []string{"TypeMeta"}, VersionField: "APIVersion", KindField: "Kind"}
s.raw.AddConversionFuncs(
s.embeddedObjectToRawExtension,
s.rawExtensionToEmbeddedObject,
@ -219,29 +217,6 @@ func (s *Scheme) Convert(in, out interface{}) error {
return s.raw.Convert(in, out)
}
// FindTypeMeta takes an arbitary api type, returns pointer to its TypeMeta field.
// obj must be a pointer to an api type.
func FindTypeMeta(obj Object) (TypeMetaInterface, error) {
v, err := enforcePtr(obj)
if err != nil {
return nil, err
}
t := v.Type()
name := t.Name()
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), name, v.Interface())
}
jsonBase := v.FieldByName("TypeMeta")
if !jsonBase.IsValid() {
return nil, fmt.Errorf("struct %v lacks embedded JSON type", name)
}
g, err := newGenericTypeMeta(jsonBase)
if err != nil {
return nil, err
}
return g, nil
}
// EncodeToVersion turns the given api object into an appropriate JSON string.
// Will return an error if the object doesn't have an embedded TypeMeta.
// Obj may be a pointer to a struct, or a struct. If a struct, a copy
@ -274,33 +249,6 @@ func (s *Scheme) EncodeToVersion(obj Object, destVersion string) (data []byte, e
return s.raw.EncodeToVersion(obj, destVersion)
}
// enforcePtr ensures that obj is a pointer of some sort. Returns a reflect.Value of the
// dereferenced pointer, ensuring that it is settable/addressable.
// Returns an error if this is not possible.
func enforcePtr(obj Object) (reflect.Value, error) {
v := reflect.ValueOf(obj)
if v.Kind() != reflect.Ptr {
return reflect.Value{}, fmt.Errorf("expected pointer, but got %v", v.Type().Name())
}
return v.Elem(), nil
}
// VersionAndKind will return the APIVersion and Kind of the given wire-format
// enconding of an APIObject, or an error.
func VersionAndKind(data []byte) (version, kind string, err error) {
findKind := struct {
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
}{}
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err = yaml.Unmarshal(data, &findKind)
if err != nil {
return "", "", fmt.Errorf("couldn't get version/kind: %v", err)
}
return findKind.APIVersion, findKind.Kind, nil
}
// Decode converts a YAML or JSON string back into a pointer to an api object.
// Deduces the type based upon the APIVersion and Kind fields, which are set
// by Encode. Only versioned objects (APIVersion != "") are accepted. The object
@ -339,28 +287,3 @@ func (s *Scheme) CopyOrDie(obj Object) Object {
}
return newObj
}
// metaInsertion implements conversion.MetaInsertionFactory, which lets the conversion
// package figure out how to encode our object's types and versions. These fields are
// located in our TypeMeta.
type metaInsertion struct {
TypeMeta struct {
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
} `json:",inline" yaml:",inline"`
}
// Create returns a new metaInsertion with the version and kind fields set.
func (metaInsertion) Create(version, kind string) interface{} {
m := metaInsertion{}
m.TypeMeta.APIVersion = version
m.TypeMeta.Kind = kind
return &m
}
// Interpret returns the version and kind information from in, which must be
// a metaInsertion pointer object.
func (metaInsertion) Interpret(in interface{}) (version, kind string) {
m := in.(*metaInsertion)
return m.TypeMeta.APIVersion, m.TypeMeta.Kind
}

View File

@ -19,8 +19,33 @@ package runtime
import (
"fmt"
"reflect"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
)
// FindTypeMeta takes an arbitary api type, returns pointer to its TypeMeta field.
// obj must be a pointer to an api type.
func FindTypeMeta(obj Object) (TypeMetaInterface, error) {
v, err := conversion.EnforcePtr(obj)
if err != nil {
return nil, err
}
t := v.Type()
name := t.Name()
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("expected struct, but got %v: %v (%#v)", v.Kind(), name, v.Interface())
}
typeMeta := v.FieldByName("TypeMeta")
if !typeMeta.IsValid() {
return nil, fmt.Errorf("struct %v lacks embedded JSON type", name)
}
g, err := newGenericTypeMeta(typeMeta)
if err != nil {
return nil, err
}
return g, nil
}
// NewTypeMetaResourceVersioner returns a ResourceVersioner that can set or
// retrieve ResourceVersion on objects derived from TypeMeta.
func NewTypeMetaResourceVersioner() ResourceVersioner {