Skip to content

Commit

Permalink
Remove codec awareness from conversion
Browse files Browse the repository at this point in the history
Allow convertors to be specialized, some cleanup to how conversion
functions are stored internally to allow better reuse.
  • Loading branch information
smarterclayton committed Jan 22, 2016
1 parent 9d5df20 commit 6582b4c
Show file tree
Hide file tree
Showing 16 changed files with 506 additions and 1,100 deletions.
122 changes: 95 additions & 27 deletions pkg/conversion/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@ type DebugLogger interface {
type Converter struct {
// Map from the conversion pair to a function which can
// do the conversion.
conversionFuncs map[typePair]reflect.Value
generatedConversionFuncs map[typePair]reflect.Value
conversionFuncs ConversionFuncs
generatedConversionFuncs ConversionFuncs

// Set of conversions that should be treated as a no-op
ignoredConversions map[typePair]struct{}

// This is a map from a source field type and name, to a list of destination
// field type and name.
Expand Down Expand Up @@ -76,21 +79,30 @@ type Converter struct {
// NewConverter creates a new Converter object.
func NewConverter() *Converter {
c := &Converter{
conversionFuncs: map[typePair]reflect.Value{},
generatedConversionFuncs: map[typePair]reflect.Value{},
defaultingFuncs: map[reflect.Type]reflect.Value{},
defaultingInterfaces: map[reflect.Type]interface{}{},
conversionFuncs: NewConversionFuncs(),
generatedConversionFuncs: NewConversionFuncs(),
ignoredConversions: make(map[typePair]struct{}),
defaultingFuncs: make(map[reflect.Type]reflect.Value),
defaultingInterfaces: make(map[reflect.Type]interface{}),
nameFunc: func(t reflect.Type) string { return t.Name() },
structFieldDests: map[typeNamePair][]typeNamePair{},
structFieldSources: map[typeNamePair][]typeNamePair{},
structFieldDests: make(map[typeNamePair][]typeNamePair),
structFieldSources: make(map[typeNamePair][]typeNamePair),

inputFieldMappingFuncs: map[reflect.Type]FieldMappingFunc{},
inputDefaultFlags: map[reflect.Type]FieldMatchingFlags{},
inputFieldMappingFuncs: make(map[reflect.Type]FieldMappingFunc),
inputDefaultFlags: make(map[reflect.Type]FieldMatchingFlags),
}
c.RegisterConversionFunc(ByteSliceCopy)
return c
}

// WithConversions returns a Converter that is a copy of c but with the additional
// fns merged on top.
func (c *Converter) WithConversions(fns ConversionFuncs) *Converter {
copied := *c
copied.conversionFuncs = c.conversionFuncs.Merge(fns)
return &copied
}

// ByteSliceCopy prevents recursing into every byte
func ByteSliceCopy(in *[]byte, out *[]byte, s Scope) error {
*out = make([]byte, len(*in))
Expand Down Expand Up @@ -130,6 +142,42 @@ type Scope interface {
// the value of the source or destination struct tags.
type FieldMappingFunc func(key string, sourceTag, destTag reflect.StructTag) (source string, dest string)

func NewConversionFuncs() ConversionFuncs {
return ConversionFuncs{fns: make(map[typePair]reflect.Value)}
}

type ConversionFuncs struct {
fns map[typePair]reflect.Value
}

// Add adds the provided conversion functions to the lookup table - they must have the signature
// `func(type1, type2, Scope) error`. Functions are added in the order passed and will override
// previously registered pairs.
func (c ConversionFuncs) Add(fns ...interface{}) error {
for _, fn := range fns {
fv := reflect.ValueOf(fn)
ft := fv.Type()
if err := verifyConversionFunctionSignature(ft); err != nil {
return err
}
c.fns[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
}
return nil
}

// Merge returns a new ConversionFuncs that contains all conversions from
// both other and c, with other conversions taking precedence.
func (c ConversionFuncs) Merge(other ConversionFuncs) ConversionFuncs {
merged := NewConversionFuncs()
for k, v := range c.fns {
merged.fns[k] = v
}
for k, v := range other.fns {
merged.fns[k] = v
}
return merged
}

// Meta is supplied by Scheme, when it calls Convert.
type Meta struct {
SrcVersion string
Expand Down Expand Up @@ -296,34 +344,44 @@ func verifyConversionFunctionSignature(ft reflect.Type) error {
// return nil
// })
func (c *Converter) RegisterConversionFunc(conversionFunc interface{}) error {
fv := reflect.ValueOf(conversionFunc)
ft := fv.Type()
if err := verifyConversionFunctionSignature(ft); err != nil {
return err
}
c.conversionFuncs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
return nil
return c.conversionFuncs.Add(conversionFunc)
}

// Similar to RegisterConversionFunc, but registers conversion function that were
// automatically generated.
func (c *Converter) RegisterGeneratedConversionFunc(conversionFunc interface{}) error {
fv := reflect.ValueOf(conversionFunc)
ft := fv.Type()
if err := verifyConversionFunctionSignature(ft); err != nil {
return err
return c.generatedConversionFuncs.Add(conversionFunc)
}

// RegisterIgnoredConversion registers a "no-op" for conversion, where any requested
// conversion between from and to is ignored.
func (c *Converter) RegisterIgnoredConversion(from, to interface{}) error {
typeFrom := reflect.TypeOf(from)
typeTo := reflect.TypeOf(to)
if reflect.TypeOf(from).Kind() != reflect.Ptr {
return fmt.Errorf("expected pointer arg for 'from' param 0, got: %v", typeFrom)
}
c.generatedConversionFuncs[typePair{ft.In(0).Elem(), ft.In(1).Elem()}] = fv
if typeTo.Kind() != reflect.Ptr {
return fmt.Errorf("expected pointer arg for 'to' param 1, got: %v", typeTo)
}
c.ignoredConversions[typePair{typeFrom.Elem(), typeTo.Elem()}] = struct{}{}
return nil
}

// IsConversionIgnored returns true if the specified objects should be dropped during
// conversion.
func (c *Converter) IsConversionIgnored(inType, outType reflect.Type) bool {
_, found := c.ignoredConversions[typePair{inType, outType}]
return found
}

func (c *Converter) HasConversionFunc(inType, outType reflect.Type) bool {
_, found := c.conversionFuncs[typePair{inType, outType}]
_, found := c.conversionFuncs.fns[typePair{inType, outType}]
return found
}

func (c *Converter) ConversionFuncValue(inType, outType reflect.Type) (reflect.Value, bool) {
value, found := c.conversionFuncs[typePair{inType, outType}]
value, found := c.conversionFuncs.fns[typePair{inType, outType}]
return value, found
}

Expand Down Expand Up @@ -509,16 +567,26 @@ func (c *Converter) convert(sv, dv reflect.Value, scope *scope) error {
fv.Call(args)
}

pair := typePair{st, dt}

// ignore conversions of this type
if _, ok := c.ignoredConversions[pair]; ok {
if c.Debug != nil {
c.Debug.Logf("Ignoring conversion of '%v' to '%v'", st, dt)
}
return nil
}

// Convert sv to dv.
if fv, ok := c.conversionFuncs[typePair{st, dt}]; ok {
if fv, ok := c.conversionFuncs.fns[pair]; ok {
if c.Debug != nil {
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
}
return c.callCustom(sv, dv, fv, scope)
}
if fv, ok := c.generatedConversionFuncs[typePair{st, dt}]; ok {
if fv, ok := c.generatedConversionFuncs.fns[pair]; ok {
if c.Debug != nil {
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
c.Debug.Logf("Calling generated conversion of '%v' to '%v'", st, dt)
}
return c.callCustom(sv, dv, fv, scope)
}
Expand Down
Loading

0 comments on commit 6582b4c

Please sign in to comment.