File: chain.go

package info (click to toggle)
golang-github-anchore-go-struct-converter 0.0~git20240925.a088364-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 144 kB
  • sloc: makefile: 59
file content (95 lines) | stat: -rw-r--r-- 2,284 bytes parent folder | download | duplicates (4)
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
}