Make api use converter package.

pull/6/head
Daniel Smith 2014-07-31 12:10:15 -07:00
parent a73e4f4623
commit 5c0f5e85e2
3 changed files with 13 additions and 565 deletions

View File

@ -1,195 +0,0 @@
/*
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 api
import (
"fmt"
"reflect"
)
type typePair struct {
source reflect.Type
dest reflect.Type
}
type debugLogger interface {
Logf(format string, args ...interface{})
}
// Converter knows how to convert one type to another.
type Converter struct {
// Map from the conversion pair to a function which can
// do the conversion.
funcs map[typePair]reflect.Value
// If true, print helpful debugging info. Quite verbose.
debug debugLogger
}
// NewConverter makes a new Converter object.
func NewConverter() *Converter {
return &Converter{
funcs: map[typePair]reflect.Value{},
}
}
// Register registers a conversion func with the Converter. conversionFunc must take
// two parameters, the input and output type. It must take a pointer to each. It must
// return an error.
//
// Example:
// c.Register(func(in *Pod, out *v1beta1.Pod) error { ... return nil })
func (c *Converter) Register(conversionFunc interface{}) error {
fv := reflect.ValueOf(conversionFunc)
ft := fv.Type()
if ft.Kind() != reflect.Func {
return fmt.Errorf("expected func, got: %v", ft)
}
if ft.NumIn() != 2 {
return fmt.Errorf("expected two in params, got: %v", ft)
}
if ft.NumOut() != 1 {
return fmt.Errorf("expected one out param, got: %v", ft)
}
if ft.In(0).Kind() != reflect.Ptr {
return fmt.Errorf("expected pointer arg for in param 0, got: %v", ft)
}
if ft.In(1).Kind() != reflect.Ptr {
return fmt.Errorf("expected pointer arg for in param 1, got: %v", ft)
}
var forErrorType error
// This convolution is necessary, otherwise TypeOf picks up on the fact
// that forErrorType is nil.
errorType := reflect.TypeOf(&forErrorType).Elem()
if ft.Out(0) != errorType {
return fmt.Errorf("expected error return, got: %v", ft)
}
c.funcs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
return nil
}
// 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.
// Not safe for objects with cyclic references!
func (c *Converter) Convert(src, dest interface{}) error {
dv, sv := reflect.ValueOf(dest), reflect.ValueOf(src)
if dv.Kind() != reflect.Ptr {
return fmt.Errorf("Need pointer, but got %#v", dest)
}
if sv.Kind() != reflect.Ptr {
return fmt.Errorf("Need pointer, but got %#v", src)
}
dv = dv.Elem()
sv = sv.Elem()
if !dv.CanAddr() {
return fmt.Errorf("Can't write to dest")
}
return c.convert(sv, dv)
}
// convert recursively copies sv into dv, calling an appropriate conversion function if
// one is registered.
func (c *Converter) convert(sv, dv reflect.Value) 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)
}
ret := fv.Call([]reflect.Value{sv.Addr(), dv.Addr()})[0].Interface()
// This convolution is necssary because nil interfaces won't convert
// to errors.
if ret == nil {
return nil
}
return ret.(error)
}
if dt.Name() != st.Name() {
return fmt.Errorf("Type names don't match: %v, %v", dt.Name(), st.Name())
}
// This should handle all simple types.
if st.AssignableTo(dt) {
dv.Set(sv)
return nil
}
if st.ConvertibleTo(dt) {
dv.Set(sv.Convert(dt))
return nil
}
if c.debug != nil {
c.debug.Logf("Trying to convert '%v' to '%v'", st, dt)
}
switch dv.Kind() {
case reflect.Struct:
for i := 0; i < dt.NumField(); i++ {
f := dv.Type().Field(i)
sf := sv.FieldByName(f.Name)
if !sf.IsValid() {
return fmt.Errorf("%v not present in source %v for dest %v", f.Name, sv.Type(), dv.Type())
}
df := dv.Field(i)
if err := c.convert(sf, df); err != nil {
return err
}
}
case reflect.Slice:
if sv.IsNil() {
// Don't make a zero-length slice.
dv.Set(reflect.Zero(dt))
return nil
}
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)); err != nil {
return err
}
}
case reflect.Ptr:
if sv.IsNil() {
// Don't copy a nil ptr!
dv.Set(reflect.Zero(dt))
return nil
}
dv.Set(reflect.New(dt.Elem()))
return c.convert(sv.Elem(), dv.Elem())
case reflect.Map:
if sv.IsNil() {
// Don't copy a nil ptr!
dv.Set(reflect.Zero(dt))
return nil
}
dv.Set(reflect.MakeMap(dt))
for _, sk := range sv.MapKeys() {
dk := reflect.New(dt.Key()).Elem()
if err := c.convert(sk, dk); err != nil {
return err
}
dkv := reflect.New(dt.Elem()).Elem()
if err := c.convert(sv.MapIndex(sk), dkv); err != nil {
return err
}
dv.SetMapIndex(dk, dkv)
}
default:
return fmt.Errorf("Couldn't copy '%v' into '%v'", st, dt)
}
return nil
}

View File

@ -1,110 +0,0 @@
/*
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 api
import (
"fmt"
"testing"
)
func TestConverter(t *testing.T) {
type A struct {
Foo string
}
type B struct {
Bar string
}
type C struct{}
c := NewConverter()
err := c.Register(func(in *A, out *B) error {
out.Bar = in.Foo
return nil
})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
err = c.Register(func(in *B, out *A) error {
out.Foo = in.Bar
return nil
})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
x := A{"hello, intrepid test reader!"}
y := B{}
err = c.Convert(&x, &y)
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if e, a := x.Foo, y.Bar; e != a {
t.Errorf("expected %v, got %v", e, a)
}
z := B{"all your test are belong to us"}
w := A{}
err = c.Convert(&z, &w)
if err != nil {
t.Fatalf("unexpected error %v", err)
}
if e, a := z.Bar, w.Foo; e != a {
t.Errorf("expected %v, got %v", e, a)
}
err = c.Register(func(in *A, out *C) error {
return fmt.Errorf("C can't store an A, silly")
})
if err != nil {
t.Fatalf("unexpected error %v", err)
}
err = c.Convert(&A{}, &C{})
if err == nil {
t.Errorf("unexpected non-error")
}
}
func anonymousEmptyTypeNamedA() interface{} {
type A struct{}
return &A{}
}
func TestCopyConvertor(t *testing.T) {
type A struct {
Bar string
}
type B struct {
Bar string
}
c := NewConverter()
err := c.Convert(anonymousEmptyTypeNamedA(), &A{})
if err == nil {
t.Errorf("unexpected non-error")
}
err = c.Convert(&A{}, anonymousEmptyTypeNamedA())
if err != nil {
t.Fatalf("unexpected error %v", err)
}
err = c.Convert(&B{}, &A{})
if err == nil {
t.Errorf("unexpected non-error")
}
}

View File

@ -17,28 +17,20 @@ limitations under the License.
package api
import (
"encoding/json"
"fmt"
"reflect"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
"github.com/GoogleCloudPlatform/kubernetes/pkg/conversion"
"gopkg.in/v1/yaml"
)
// versionMap allows one to figure out the go type of an object with
// the given version and name.
var versionMap = map[string]map[string]reflect.Type{}
// typeToVersion allows one to figure out the version for a given go object.
// The reflect.Type we index by should *not* be a pointer. If the same type
// is registered for multiple versions, the last one wins.
var typeToVersion = map[reflect.Type]string{}
// theConverter stores all registered conversion functions. It also has
// default coverting behavior.
var theConverter = NewConverter()
var conversionScheme *conversion.Scheme
func init() {
conversionScheme = conversion.NewScheme()
conversionScheme.InternalVersion = ""
conversionScheme.ExternalVersion = "v1beta1"
AddKnownTypes("",
PodList{},
Pod{},
@ -98,31 +90,13 @@ func init() {
// AddKnownTypes registers the types of the arguments to the marshaller of the package api.
// Encode() refuses the object unless its type is registered with AddKnownTypes.
func AddKnownTypes(version string, types ...interface{}) {
knownTypes, found := versionMap[version]
if !found {
knownTypes = map[string]reflect.Type{}
versionMap[version] = knownTypes
}
for _, obj := range types {
t := reflect.TypeOf(obj)
if t.Kind() != reflect.Struct {
panic("All types must be structs.")
}
knownTypes[t.Name()] = t
typeToVersion[t] = version
}
conversionScheme.AddKnownTypes(version, types...)
}
// New returns a new API object of the given version ("" for internal
// representation) and name, or an error if it hasn't been registered.
func New(versionName, typeName string) (interface{}, error) {
if types, ok := versionMap[versionName]; ok {
if t, ok := types[typeName]; ok {
return reflect.New(t).Interface(), nil
}
return nil, fmt.Errorf("No type '%v' for version '%v'", typeName, versionName)
}
return nil, fmt.Errorf("No version '%v'", versionName)
return conversionScheme.NewObject(versionName, typeName)
}
// AddConversionFuncs adds a function to the list of conversion functions. The given
@ -138,20 +112,14 @@ func New(versionName, typeName string) (interface{}, error) {
// extra fields, but it must not remove any. So you only need to add a conversion
// function for things with changed/removed fields.
func AddConversionFuncs(conversionFuncs ...interface{}) error {
for _, f := range conversionFuncs {
err := theConverter.Register(f)
if err != nil {
return err
}
}
return nil
return conversionScheme.AddConversionFuncs(conversionFuncs...)
}
// Convert will attempt to convert in into out. Both must be pointers to API objects.
// For easy testing of conversion functions. Returns an error if the conversion isn't
// possible.
func Convert(in, out interface{}) error {
return theConverter.Convert(in, out)
return conversionScheme.Convert(in, out)
}
// FindJSONBase takes an arbitary api type, returns pointer to its JSONBase field.
@ -196,11 +164,7 @@ func FindJSONBaseRO(obj interface{}) (JSONBase, error) {
// EncodeOrDie is a version of Encode which will panic instead of returning an error. For tests.
func EncodeOrDie(obj interface{}) string {
bytes, err := Encode(obj)
if err != nil {
panic(err)
}
return string(bytes)
return conversionScheme.EncodeOrDie(obj)
}
// Encode turns the given api object into an appropriate JSON string.
@ -238,93 +202,7 @@ func EncodeOrDie(obj interface{}) string {
// upgraded.
//
func Encode(obj interface{}) (data []byte, err error) {
obj = maybeCopy(obj)
obj, err = maybeExternalize(obj)
if err != nil {
return nil, err
}
jsonBase, err := prepareEncode(obj)
if err != nil {
return nil, err
}
data, err = json.MarshalIndent(obj, "", " ")
if err != nil {
return nil, err
}
// Leave these blank in memory.
jsonBase.SetKind("")
jsonBase.SetAPIVersion("")
return data, err
}
// Returns the API version of the go object, or an error if it's not a
// pointer or is unregistered.
func objAPIVersionAndName(obj interface{}) (apiVersion, name string, err error) {
v, err := enforcePtr(obj)
if err != nil {
return "", "", err
}
t := v.Type()
if version, ok := typeToVersion[t]; !ok {
return "", "", fmt.Errorf("Unregistered type: %v", t)
} else {
return version, t.Name(), nil
}
}
// maybeExternalize converts obj to an external object if it isn't one already.
// obj must be a pointer.
func maybeExternalize(obj interface{}) (interface{}, error) {
version, _, err := objAPIVersionAndName(obj)
if err != nil {
return nil, err
}
if version != "" {
// Object is already of an external versioned type.
return obj, nil
}
return externalize(obj)
}
// maybeCopy copies obj if it is not a pointer, to get a settable/addressable
// object. Guaranteed to return a pointer.
func maybeCopy(obj interface{}) interface{} {
v := reflect.ValueOf(obj)
if v.Kind() == reflect.Ptr {
return obj
}
v2 := reflect.New(v.Type())
v2.Elem().Set(v)
return v2.Interface()
}
// prepareEncode sets the APIVersion and Kind fields to match the go type in obj.
// Returns an error if the (version, name) pair isn't registered for the type or
// if the type is an internal, non-versioned object.
func prepareEncode(obj interface{}) (JSONBaseInterface, error) {
version, name, err := objAPIVersionAndName(obj)
if err != nil {
return nil, err
}
if version == "" {
return nil, fmt.Errorf("No version for '%v' (%#v); extremely inadvisable to write it in wire format.", name, obj)
}
jsonBase, err := FindJSONBase(obj)
if err != nil {
return nil, err
}
knownTypes, found := versionMap[version]
if !found {
return nil, fmt.Errorf("struct %s, %s won't be unmarshalable because it's not in known versions", version, name)
}
if _, contains := knownTypes[name]; !contains {
return nil, fmt.Errorf("struct %s, %s won't be unmarshalable because it's not in knownTypes", version, name)
}
jsonBase.SetAPIVersion(version)
jsonBase.SetKind(name)
return jsonBase, nil
return conversionScheme.Encode(obj)
}
// Ensures that obj is a pointer of some sort. Returns a reflect.Value of the
@ -359,35 +237,7 @@ func VersionAndKind(data []byte) (version, kind string, err error) {
// by Encode. Only versioned objects (APIVersion != "") are accepted. The object
// will be converted into the in-memory unversioned type before being returned.
func Decode(data []byte) (interface{}, error) {
version, kind, err := VersionAndKind(data)
if err != nil {
return nil, err
}
if version == "" {
return nil, fmt.Errorf("API Version not set in '%s'", string(data))
}
obj, err := New(version, kind)
if err != nil {
return nil, fmt.Errorf("Unable to create new object of type ('%s', '%s')", version, kind)
}
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err = yaml.Unmarshal(data, obj)
if err != nil {
return nil, err
}
obj, err = internalize(obj)
if err != nil {
return nil, err
}
jsonBase, err := FindJSONBase(obj)
if err != nil {
return nil, err
}
// Don't leave these set. Type and version info is deducible from go's type.
jsonBase.SetKind("")
jsonBase.SetAPIVersion("")
return obj, nil
return conversionScheme.Decode(data)
}
// DecodeInto parses a YAML or JSON string and stores it in obj. Returns an error
@ -396,102 +246,5 @@ func Decode(data []byte) (interface{}, error) {
// If obj's APIVersion doesn't match that in data, an attempt will be made to convert
// data into obj's version.
func DecodeInto(data []byte, obj interface{}) error {
dataVersion, dataKind, err := VersionAndKind(data)
if err != nil {
return err
}
objVersion, objKind, err := objAPIVersionAndName(obj)
if err != nil {
return err
}
if dataKind == "" {
// Assume objects with unset Kind fields are being unmarshalled into the
// correct type.
dataKind = objKind
}
if dataKind != objKind {
return fmt.Errorf("data of kind '%v', obj of type '%v'", dataKind, objKind)
}
if dataVersion == "" {
// Assume objects with unset Version fields are being unmarshalled into the
// correct type.
dataVersion = objVersion
}
if objVersion == dataVersion {
// Easy case!
err = yaml.Unmarshal(data, obj)
if err != nil {
return err
}
} else {
// TODO: look up in our map to see if we can do this dataVersion -> objVersion
// conversion.
if objVersion != "" || dataVersion != "v1beta1" {
return fmt.Errorf("Can't convert from '%v' to '%v' for type '%v'", dataVersion, objVersion, dataKind)
}
external, err := New(dataVersion, dataKind)
if err != nil {
return fmt.Errorf("Unable to create new object of type ('%s', '%s')", dataVersion, dataKind)
}
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
err = yaml.Unmarshal(data, external)
if err != nil {
return err
}
internal, err := internalize(external)
if err != nil {
return err
}
// Copy to the provided object.
vObj := reflect.ValueOf(obj)
vInternal := reflect.ValueOf(internal)
if !vInternal.Type().AssignableTo(vObj.Type()) {
return fmt.Errorf("%s is not assignable to %s", vInternal.Type(), vObj.Type())
}
vObj.Elem().Set(vInternal.Elem())
}
jsonBase, err := FindJSONBase(obj)
if err != nil {
return err
}
// Don't leave these set. Type and version info is deducible from go's type.
jsonBase.SetKind("")
jsonBase.SetAPIVersion("")
return nil
}
func internalize(obj interface{}) (interface{}, error) {
_, objKind, err := objAPIVersionAndName(obj)
if err != nil {
return nil, err
}
objOut, err := New("", objKind)
if err != nil {
return nil, err
}
err = theConverter.Convert(obj, objOut)
if err != nil {
return nil, err
}
return objOut, nil
}
func externalize(obj interface{}) (interface{}, error) {
_, objKind, err := objAPIVersionAndName(obj)
if err != nil {
return nil, err
}
objOut, err := New("v1beta1", objKind)
if err != nil {
return nil, err
}
err = theConverter.Convert(obj, objOut)
if err != nil {
return nil, err
}
return objOut, nil
return conversionScheme.DecodeInto(data, obj)
}