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