summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/stretchr/objx/accessors.go
blob: 721bcac79939349a05ecf8cd154a4cec4886a055 (plain)
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package objx

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// arrayAccesRegexString is the regex used to extract the array number
// from the access path
const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$`

// arrayAccesRegex is the compiled arrayAccesRegexString
var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString)

// Get gets the value using the specified selector and
// returns it inside a new Obj object.
//
// If it cannot find the value, Get will return a nil
// value inside an instance of Obj.
//
// Get can only operate directly on map[string]interface{} and []interface.
//
// Example
//
// To access the title of the third chapter of the second book, do:
//
//    o.Get("books[1].chapters[2].title")
func (m Map) Get(selector string) *Value {
	rawObj := access(m, selector, nil, false, false)
	return &Value{data: rawObj}
}

// Set sets the value using the specified selector and
// returns the object on which Set was called.
//
// Set can only operate directly on map[string]interface{} and []interface
//
// Example
//
// To set the title of the third chapter of the second book, do:
//
//    o.Set("books[1].chapters[2].title","Time to Go")
func (m Map) Set(selector string, value interface{}) Map {
	access(m, selector, value, true, false)
	return m
}

// access accesses the object using the selector and performs the
// appropriate action.
func access(current, selector, value interface{}, isSet, panics bool) interface{} {

	switch selector.(type) {
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:

		if array, ok := current.([]interface{}); ok {
			index := intFromInterface(selector)

			if index >= len(array) {
				if panics {
					panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
				}
				return nil
			}

			return array[index]
		}

		return nil

	case string:

		selStr := selector.(string)
		selSegs := strings.SplitN(selStr, PathSeparator, 2)
		thisSel := selSegs[0]
		index := -1
		var err error

		// https://github.com/stretchr/objx/issues/12
		if strings.Contains(thisSel, "[") {

			arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel)

			if len(arrayMatches) > 0 {

				// Get the key into the map
				thisSel = arrayMatches[1]

				// Get the index into the array at the key
				index, err = strconv.Atoi(arrayMatches[2])

				if err != nil {
					// This should never happen. If it does, something has gone
					// seriously wrong. Panic.
					panic("objx: Array index is not an integer.  Must use array[int].")
				}

			}
		}

		if curMap, ok := current.(Map); ok {
			current = map[string]interface{}(curMap)
		}

		// get the object in question
		switch current.(type) {
		case map[string]interface{}:
			curMSI := current.(map[string]interface{})
			if len(selSegs) <= 1 && isSet {
				curMSI[thisSel] = value
				return nil
			} else {
				current = curMSI[thisSel]
			}
		default:
			current = nil
		}

		if current == nil && panics {
			panic(fmt.Sprintf("objx: '%v' invalid on object.", selector))
		}

		// do we need to access the item of an array?
		if index > -1 {
			if array, ok := current.([]interface{}); ok {
				if index < len(array) {
					current = array[index]
				} else {
					if panics {
						panic(fmt.Sprintf("objx: Index %d is out of range. Slice only contains %d items.", index, len(array)))
					}
					current = nil
				}
			}
		}

		if len(selSegs) > 1 {
			current = access(current, selSegs[1], value, isSet, panics)
		}

	}

	return current

}

// intFromInterface converts an interface object to the largest
// representation of an unsigned integer using a type switch and
// assertions
func intFromInterface(selector interface{}) int {
	var value int
	switch selector.(type) {
	case int:
		value = selector.(int)
	case int8:
		value = int(selector.(int8))
	case int16:
		value = int(selector.(int16))
	case int32:
		value = int(selector.(int32))
	case int64:
		value = int(selector.(int64))
	case uint:
		value = int(selector.(uint))
	case uint8:
		value = int(selector.(uint8))
	case uint16:
		value = int(selector.(uint16))
	case uint32:
		value = int(selector.(uint32))
	case uint64:
		value = int(selector.(uint64))
	default:
		panic("objx: array access argument is not an integer type (this should never happen)")
	}

	return value
}