-
Notifications
You must be signed in to change notification settings - Fork 6
/
utils.go
211 lines (192 loc) · 5.71 KB
/
utils.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
package transform
import (
"encoding/json"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"github.com/GannettDigital/jsonparser"
)
var indexRe = regexp.MustCompile(`\[([\d]+)\]`)
// Concat will combine any two arbitrary values, though only strings are supported for non-trivial concatenation.
func concat(a, b interface{}, delimiter string) (interface{}, error) {
switch {
case a == nil && b == nil:
return nil, nil
case a == nil:
return b, nil
case b == nil:
return a, nil
}
atype := reflect.TypeOf(a).String()
btype := reflect.TypeOf(b).String()
if atype != btype {
return nil, fmt.Errorf("can't concat types %q and %q", atype, btype)
}
switch a.(type) {
case string:
if delimiter != "" && a.(string) != "" && b.(string) != "" {
return a.(string) + delimiter + b.(string), nil
}
return a.(string) + b.(string), nil
default:
return nil, fmt.Errorf("concatenation of types %q not supported", atype)
}
}
// convert takes the raw value and checks to see if it matches the jsonType, if not it will attempt to convert it
// to the correct type. The function does not set defaults so a nil value will be returned as nil not as the desired
// types empty type.
func convert(raw interface{}, jsonType string) (interface{}, error) {
if raw == nil {
return nil, nil
}
// Since jsonpath returns an array from filters, we want to check if there's a single element that should be extracted
// for non-array return types being used.
// We also want to recursively handle nested arrays at the same time during this extraction
if rawArray, ok := raw.([]interface{}); ok && (len(rawArray) == 1 || (len(rawArray) > 0 && jsonType != "array")) {
raw = rawArray[0]
if nestedArray, ok := raw.([]interface{}); ok && len(nestedArray) > 0 {
return convert(nestedArray[0], jsonType)
}
} else if ok && len(rawArray) == 0 {
return nil, nil
}
switch jsonType {
case "boolean":
return convertBoolean(raw)
case "number", "integer":
return convertNumber(raw)
case "string":
return convertString(raw)
case "date-time":
return convertDateTime(raw)
}
return raw, nil
}
func convertBoolean(raw interface{}) (interface{}, error) {
switch t := raw.(type) {
case bool:
return raw, nil
case string:
if t == "" {
return false, nil
}
return strconv.ParseBool(t)
case int:
return t > 0, nil
case float32:
return t > 0, nil
case float64:
return t > 0, nil
case nil:
return nil, nil
default:
return nil, fmt.Errorf("unable to convert type %q to boolean", reflect.TypeOf(raw))
}
}
func convertNumber(raw interface{}) (interface{}, error) {
switch t := raw.(type) {
case bool:
if t {
return 1, nil
}
return 0, nil
case string:
if t == "" {
return nil, nil
}
if value, err := strconv.Atoi(t); err == nil {
return value, nil
}
if value, err := strconv.ParseFloat(t, 64); err == nil {
return value, nil
}
return nil, fmt.Errorf("failed to convert string %q to number", t)
case int, float32, float64:
return raw, nil
default:
return nil, fmt.Errorf("unable to convert type %q to a number", reflect.TypeOf(raw))
}
}
func convertDateTime(raw interface{}) (interface{}, error) {
switch t := raw.(type) {
case string:
if t == "" {
return nil, nil
}
return time.Parse(time.RFC3339, t)
case int:
return time.Unix(int64(t), 0).UTC(), nil
case float64:
return time.Unix(int64(t), 0).UTC(), nil
default:
return nil, fmt.Errorf("unable to convert type %q to a date-time", reflect.TypeOf(raw))
}
}
func convertString(raw interface{}) (interface{}, error) {
switch t := raw.(type) {
case bool:
return strconv.FormatBool(t), nil
case string:
return raw, nil
case int:
return strconv.Itoa(t), nil
case uint64:
return strconv.FormatUint(t, 10), nil
case float32:
t64 := float64(t)
return strconv.FormatFloat(t64, 'f', -1, 32), nil
case float64:
return strconv.FormatFloat(t, 'f', -1, 64), nil
case nil:
return nil, nil
default:
return nil, fmt.Errorf("unable to convert type %q to a string", reflect.TypeOf(raw))
}
}
func extractTransformInstructions(raw json.RawMessage, transformIdentifier, path string, instanceType string) (*transformInstructions, error) {
rawTransformInstruction, _, _, err := jsonparser.Get(raw, "transform", transformIdentifier)
if err != nil && err != jsonparser.KeyPathNotFoundError {
return nil, fmt.Errorf("failed to extract raw instance transform: %v", err)
} else if len(rawTransformInstruction) == 0 {
return nil, nil
}
var parentPath string
if instanceType == "scalar" && strings.HasSuffix(path, "[*]") && !strings.HasSuffix(path, ".[*]") {
parentPath = path
} else {
splits := strings.Split(path, ".")
parentPath = strings.Join(splits[:len(splits)-1], ".")
}
var tis transformInstructions
if err := json.Unmarshal(rawTransformInstruction, &tis); err != nil {
return nil, fmt.Errorf("failed to unmarshal instance transform: %v", err)
}
// replaces the @. format
tis.replaceJSONPathPrefix("@.", parentPath+".")
// replaces the @[] format
tis.replaceJSONPathPrefix("@[", parentPath+"[")
return &tis, nil
}
// schemaDefault determines the default for an instance based on the JSONSchema.
// If no default is defined nil is returned.
func schemaDefault(schema json.RawMessage) (interface{}, error) {
ifields := struct {
Default interface{} `json:"default"`
}{}
if err := json.Unmarshal(schema, &ifields); err != nil {
return nil, fmt.Errorf("failed to extract schema default: %v", err)
}
// Try the default
if ifields.Default != nil {
return ifields.Default, nil
}
return nil, nil
}
// replaceIndex takes a path which may include array index values like `a[0].b.c[23].d` with the index values replaced
// with "*", ie `a[*].b.c[*].d`.
func replaceIndex(path string) string {
return indexRe.ReplaceAllString(path, "[*]")
}