1package awsutil
2
3import (
4	"reflect"
5	"regexp"
6	"strconv"
7	"strings"
8
9	"github.com/jmespath/go-jmespath"
10)
11
12var indexRe = regexp.MustCompile(`(.+)\[(-?\d+)?\]$`)
13
14// rValuesAtPath returns a slice of values found in value v. The values
15// in v are explored recursively so all nested values are collected.
16func rValuesAtPath(v interface{}, path string, createPath, caseSensitive, nilTerm bool) []reflect.Value {
17	pathparts := strings.Split(path, "||")
18	if len(pathparts) > 1 {
19		for _, pathpart := range pathparts {
20			vals := rValuesAtPath(v, pathpart, createPath, caseSensitive, nilTerm)
21			if len(vals) > 0 {
22				return vals
23			}
24		}
25		return nil
26	}
27
28	values := []reflect.Value{reflect.Indirect(reflect.ValueOf(v))}
29	components := strings.Split(path, ".")
30	for len(values) > 0 && len(components) > 0 {
31		var index *int64
32		var indexStar bool
33		c := strings.TrimSpace(components[0])
34		if c == "" { // no actual component, illegal syntax
35			return nil
36		} else if caseSensitive && c != "*" && strings.ToLower(c[0:1]) == c[0:1] {
37			// TODO normalize case for user
38			return nil // don't support unexported fields
39		}
40
41		// parse this component
42		if m := indexRe.FindStringSubmatch(c); m != nil {
43			c = m[1]
44			if m[2] == "" {
45				index = nil
46				indexStar = true
47			} else {
48				i, _ := strconv.ParseInt(m[2], 10, 32)
49				index = &i
50				indexStar = false
51			}
52		}
53
54		nextvals := []reflect.Value{}
55		for _, value := range values {
56			// pull component name out of struct member
57			if value.Kind() != reflect.Struct {
58				continue
59			}
60
61			if c == "*" { // pull all members
62				for i := 0; i < value.NumField(); i++ {
63					if f := reflect.Indirect(value.Field(i)); f.IsValid() {
64						nextvals = append(nextvals, f)
65					}
66				}
67				continue
68			}
69
70			value = value.FieldByNameFunc(func(name string) bool {
71				if c == name {
72					return true
73				} else if !caseSensitive && strings.EqualFold(name, c) {
74					return true
75				}
76				return false
77			})
78
79			if nilTerm && value.Kind() == reflect.Ptr && len(components[1:]) == 0 {
80				if !value.IsNil() {
81					value.Set(reflect.Zero(value.Type()))
82				}
83				return []reflect.Value{value}
84			}
85
86			if createPath && value.Kind() == reflect.Ptr && value.IsNil() {
87				// TODO if the value is the terminus it should not be created
88				// if the value to be set to its position is nil.
89				value.Set(reflect.New(value.Type().Elem()))
90				value = value.Elem()
91			} else {
92				value = reflect.Indirect(value)
93			}
94
95			if value.Kind() == reflect.Slice || value.Kind() == reflect.Map {
96				if !createPath && value.IsNil() {
97					value = reflect.ValueOf(nil)
98				}
99			}
100
101			if value.IsValid() {
102				nextvals = append(nextvals, value)
103			}
104		}
105		values = nextvals
106
107		if indexStar || index != nil {
108			nextvals = []reflect.Value{}
109			for _, valItem := range values {
110				value := reflect.Indirect(valItem)
111				if value.Kind() != reflect.Slice {
112					continue
113				}
114
115				if indexStar { // grab all indices
116					for i := 0; i < value.Len(); i++ {
117						idx := reflect.Indirect(value.Index(i))
118						if idx.IsValid() {
119							nextvals = append(nextvals, idx)
120						}
121					}
122					continue
123				}
124
125				// pull out index
126				i := int(*index)
127				if i >= value.Len() { // check out of bounds
128					if createPath {
129						// TODO resize slice
130					} else {
131						continue
132					}
133				} else if i < 0 { // support negative indexing
134					i = value.Len() + i
135				}
136				value = reflect.Indirect(value.Index(i))
137
138				if value.Kind() == reflect.Slice || value.Kind() == reflect.Map {
139					if !createPath && value.IsNil() {
140						value = reflect.ValueOf(nil)
141					}
142				}
143
144				if value.IsValid() {
145					nextvals = append(nextvals, value)
146				}
147			}
148			values = nextvals
149		}
150
151		components = components[1:]
152	}
153	return values
154}
155
156// ValuesAtPath returns a list of values at the case insensitive lexical
157// path inside of a structure.
158func ValuesAtPath(i interface{}, path string) ([]interface{}, error) {
159	result, err := jmespath.Search(path, i)
160	if err != nil {
161		return nil, err
162	}
163
164	v := reflect.ValueOf(result)
165	if !v.IsValid() || (v.Kind() == reflect.Ptr && v.IsNil()) {
166		return nil, nil
167	}
168	if s, ok := result.([]interface{}); ok {
169		return s, err
170	}
171	if v.Kind() == reflect.Map && v.Len() == 0 {
172		return nil, nil
173	}
174	if v.Kind() == reflect.Slice {
175		out := make([]interface{}, v.Len())
176		for i := 0; i < v.Len(); i++ {
177			out[i] = v.Index(i).Interface()
178		}
179		return out, nil
180	}
181
182	return []interface{}{result}, nil
183}
184
185// SetValueAtPath sets a value at the case insensitive lexical path inside
186// of a structure.
187func SetValueAtPath(i interface{}, path string, v interface{}) {
188	rvals := rValuesAtPath(i, path, true, false, v == nil)
189	for _, rval := range rvals {
190		if rval.Kind() == reflect.Ptr && rval.IsNil() {
191			continue
192		}
193		setValue(rval, v)
194	}
195}
196
197func setValue(dstVal reflect.Value, src interface{}) {
198	if dstVal.Kind() == reflect.Ptr {
199		dstVal = reflect.Indirect(dstVal)
200	}
201	srcVal := reflect.ValueOf(src)
202
203	if !srcVal.IsValid() { // src is literal nil
204		if dstVal.CanAddr() {
205			// Convert to pointer so that pointer's value can be nil'ed
206			//                     dstVal = dstVal.Addr()
207		}
208		dstVal.Set(reflect.Zero(dstVal.Type()))
209
210	} else if srcVal.Kind() == reflect.Ptr {
211		if srcVal.IsNil() {
212			srcVal = reflect.Zero(dstVal.Type())
213		} else {
214			srcVal = reflect.ValueOf(src).Elem()
215		}
216		dstVal.Set(srcVal)
217	} else {
218		dstVal.Set(srcVal)
219	}
220
221}
222