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