-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathchain.go
95 lines (78 loc) · 2.23 KB
/
chain.go
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
}