1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
|
package converter
import (
"fmt"
"reflect"
)
// NewChain takes a set of structs, in order, to allow for accurate chain.Convert(from, &to) calls. NewChain should
// be called with struct values in a manner similar to this:
// converter.NewChain(v1.Document{}, v2.Document{}, v3.Document{})
func NewChain(structs ...interface{}) Chain {
out := Chain{}
for _, s := range structs {
typ := reflect.TypeOf(s)
if isPtr(typ) { // these shouldn't be pointers, but check just to be safe
typ = typ.Elem()
}
out.Types = append(out.Types, typ)
}
return out
}
// Chain holds a set of types with which to migrate through when a `chain.Convert` call is made
type Chain struct {
Types []reflect.Type
}
// Convert converts from one type in the chain to the target type, calling each conversion in between
func (c Chain) Convert(from interface{}, to interface{}) (err error) {
fromValue := reflect.ValueOf(from)
fromType := fromValue.Type()
// handle incoming pointers
for isPtr(fromType) {
fromValue = fromValue.Elem()
fromType = fromType.Elem()
}
toValuePtr := reflect.ValueOf(to)
toTypePtr := toValuePtr.Type()
if !isPtr(toTypePtr) {
return fmt.Errorf("TO struct provided not a pointer, unable to set values: %v", to)
}
// toValue must be a pointer but need a reference to the struct type directly
toValue := toValuePtr.Elem()
toType := toValue.Type()
fromIdx := -1
toIdx := -1
for i, typ := range c.Types {
if typ == fromType {
fromIdx = i
}
if typ == toType {
toIdx = i
}
}
if fromIdx == -1 {
return fmt.Errorf("invalid FROM type provided, not in the conversion chain: %s", fromType.Name())
}
if toIdx == -1 {
return fmt.Errorf("invalid TO type provided, not in the conversion chain: %s", toType.Name())
}
last := from
for i := fromIdx; i != toIdx; {
// skip the first index, because that is the from type - start with the next conversion in the chain
if fromIdx < toIdx {
i++
} else {
i--
}
var next interface{}
if i == toIdx {
next = to
} else {
nextVal := reflect.New(c.Types[i])
next = nextVal.Interface() // this will be a pointer, which is fine to pass to both from and to in Convert
}
if err = Convert(last, next); err != nil {
return err
}
last = next
}
return nil
}
|