chain.go 2.2 KB

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