forked from launchdarkly/go-server-sdk
/
util.go
224 lines (204 loc) · 6.21 KB
/
util.go
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package ldclient
import (
"encoding/json"
"fmt"
"net/http"
"reflect"
"time"
)
// HttpStatusError describes an http error
//
// Deprecated: this type is for internal use and will be removed in a future version.
type HttpStatusError struct {
Message string
Code int
}
// Error returns a the error message for an http status error
func (e HttpStatusError) Error() string {
return e.Message
}
// ParseTime converts any of the following into a pointer to a time.Time value:
// RFC3339/ISO8601 timestamp (example: 2016-04-16T17:09:12.759-07:00)
// Unix epoch milliseconds as string
// Unix milliseconds as number
// Passing in a time.Time value will return a pointer to the input value.
// Unparsable inputs will return nil
// More info on RFC3339: http://stackoverflow.com/questions/522251/whats-the-difference-between-iso-8601-and-rfc-3339-date-formats
//
// Deprecated: this function is for internal use and will be removed in a future version.
func ParseTime(input interface{}) *time.Time {
if input == nil {
return nil
}
// First check if we can easily detect the type as a time.Time or timestamp as string
switch typedInput := input.(type) {
case time.Time:
return &typedInput
case string:
value, err := time.Parse(time.RFC3339Nano, typedInput)
if err == nil {
utcValue := value.UTC()
return &utcValue
}
}
// Is it a number or can it be parsed as a number?
parsedNumberPtr := ParseFloat64(input)
if parsedNumberPtr != nil {
value := unixMillisToUtcTime(*parsedNumberPtr)
return &value
}
return nil
}
// ParseFloat64 parses a numeric value as float64 from a string or another numeric type.
// Returns nil pointer if input is nil or unparsable.
//
// Deprecated: this function is for internal use and will be removed in a future version.
func ParseFloat64(input interface{}) *float64 {
if input == nil {
return nil
}
switch typedInput := input.(type) {
case float64:
return &typedInput
default:
float64Type := reflect.TypeOf(float64(0))
v := reflect.ValueOf(input)
v = reflect.Indirect(v)
if v.Type().ConvertibleTo(float64Type) {
floatValue := v.Convert(float64Type)
f64 := floatValue.Float()
return &f64
}
}
return nil
}
// unixMillisToUtcTime converts a Unix epoch milliseconds float64 value to the equivalent time.Time value with UTC location
func unixMillisToUtcTime(unixMillis float64) time.Time {
return time.Unix(0, int64(unixMillis)*int64(time.Millisecond)).UTC()
}
// ToJsonRawMessage converts input to a *json.RawMessage if possible.
//
// Deprecated: this function is for internal use and will be removed in a future version.
func ToJsonRawMessage(input interface{}) (json.RawMessage, error) {
if input == nil {
return nil, nil
}
switch typedInput := input.(type) {
//already json, so just return casted input value
case json.RawMessage:
return typedInput, nil
case []byte:
inputJsonRawMessage := json.RawMessage(typedInput)
return inputJsonRawMessage, nil
default:
inputJsonBytes, err := json.Marshal(input)
if err != nil {
return nil, fmt.Errorf("Could not marshal: %+v to json", input)
}
inputJsonRawMessage := json.RawMessage(inputJsonBytes)
return inputJsonRawMessage, nil
}
}
func checkForHttpError(statusCode int, url string) error {
if statusCode == http.StatusUnauthorized {
return HttpStatusError{
Message: fmt.Sprintf("Invalid SDK key when accessing URL: %s. Verify that your SDK key is correct.", url),
Code: statusCode}
}
if statusCode == http.StatusNotFound {
return HttpStatusError{
Message: fmt.Sprintf("Resource not found when accessing URL: %s. Verify that this resource exists.", url),
Code: statusCode}
}
if statusCode/100 != 2 {
return HttpStatusError{
Message: fmt.Sprintf("Unexpected response code: %d when accessing URL: %s", statusCode, url),
Code: statusCode}
}
return nil
}
// MakeAllVersionedDataMap returns a map of version objects grouped by namespace that can be used to initialize a feature store
//
// Deprecated: this functioin is for internal use and will be removed in a future version.
func MakeAllVersionedDataMap(
features map[string]*FeatureFlag,
segments map[string]*Segment) map[VersionedDataKind]map[string]VersionedData {
allData := make(map[VersionedDataKind]map[string]VersionedData)
allData[Features] = make(map[string]VersionedData)
allData[Segments] = make(map[string]VersionedData)
for k, v := range features {
allData[Features][k] = v
}
for k, v := range segments {
allData[Segments][k] = v
}
return allData
}
func addBaseHeaders(req *http.Request, sdkKey string, config Config) {
req.Header.Add("Authorization", sdkKey)
req.Header.Add("User-Agent", config.UserAgent)
if config.WrapperName != "" {
w := config.WrapperName
if config.WrapperVersion != "" {
w = w + "/" + config.WrapperVersion
}
req.Header.Add("X-LaunchDarkly-Wrapper", w)
}
}
// Tests whether an HTTP error status represents a condition that might resolve on its own if we retry,
// or at least should not make us permanently stop sending requests.
func isHTTPErrorRecoverable(statusCode int) bool {
if statusCode >= 400 && statusCode < 500 {
switch statusCode {
case 400: // bad request
return true
case 408: // request timeout
return true
case 429: // too many requests
return true
default:
return false // all other 4xx errors are unrecoverable
}
}
return true
}
func httpErrorMessage(statusCode int, context string, recoverableMessage string) string {
statusDesc := ""
if statusCode == 401 {
statusDesc = " (invalid SDK key)"
}
resultMessage := recoverableMessage
if !isHTTPErrorRecoverable(statusCode) {
resultMessage = "giving up permanently"
}
return fmt.Sprintf("Received HTTP error %d%s for %s - %s",
statusCode, statusDesc, context, resultMessage)
}
func describeUserForErrorLog(user *User, logUserKeyInErrors bool) string {
if logUserKeyInErrors {
key := ""
if user != nil && user.Key != nil {
key = *user.Key
}
return fmt.Sprintf("user '%s'", key)
}
return "a user (enable LogUserKeyInErrors to see the user key)"
}
func stringSlicesEqual(a []string, b []string) bool {
if len(a) != len(b) {
return false
}
for _, n0 := range a {
ok := false
for _, n1 := range b {
if n1 == n0 {
ok = true
break
}
}
if !ok {
return false
}
}
return true
}