Make needed changes in conversion package to support pluggability

This commit is contained in:
Daniel Smith
2014-09-09 22:51:33 -07:00
parent 91e9089819
commit 71e547124c
6 changed files with 448 additions and 68 deletions

View File

@@ -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