-
Notifications
You must be signed in to change notification settings - Fork 553
/
Copy pathconfig.go
304 lines (254 loc) · 6.91 KB
/
config.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
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
// The reason that so many of these functions seem like duplication is simply for the better error
// messages so each driver doesn't need to deal with validation on these simple field types
package drivers
import (
"os"
"strconv"
"strings"
"github.com/friendsofgo/errors"
)
// Config is a map with helper functions
type Config map[string]interface{}
// MustString retrieves a string that must exist and must be a string, it must also not be the empty string
func (c Config) MustString(key string) string {
s, ok := c[key]
if !ok {
panic(errors.Errorf("failed to find key %s in config", key))
}
str, ok := s.(string)
if !ok {
panic(errors.Errorf("found key %s in config, but it was not a string (%T)", key, s))
}
if len(str) == 0 {
panic(errors.Errorf("found key %s in config, but it was an empty string", key))
}
return str
}
// MustInt retrieves a string that must exist and must be an int, and it must not be 0
func (c Config) MustInt(key string) int {
i, ok := c[key]
if !ok {
panic(errors.Errorf("failed to find key %s in config", key))
}
var integer int
switch t := i.(type) {
case int:
integer = t
case float64:
integer = int(t)
case string:
var err error
integer, err = strconv.Atoi(t)
if err != nil {
panic(errors.Errorf("failed to parse key %s (%s) to int: %v", key, t, err))
}
default:
panic(errors.Errorf("found key %s in config, but it was not an int (%T)", key, i))
}
if integer == 0 {
panic(errors.Errorf("found key %s in config, but its value was 0", key))
}
return integer
}
// String retrieves a non-empty string, the bool says if it exists, is of appropriate type,
// and has a non-zero length or not.
func (c Config) String(key string) (string, bool) {
s, ok := c[key]
if !ok {
return "", false
}
str, ok := s.(string)
if !ok {
return "", false
}
if len(str) == 0 {
return "", false
}
return str, true
}
// DefaultString retrieves a non-empty string or the default value provided.
func (c Config) DefaultString(key, def string) string {
str, ok := c.String(key)
if !ok {
return def
}
return str
}
// DefaultBool retrieves a non-empty bool or the default value provided.
func (c Config) DefaultBool(key string, def bool) bool {
b, ok := c[key]
if !ok {
return def
}
bul, ok := b.(bool)
if !ok {
return def
}
return bul
}
// Int retrieves an int, the bool says if it exists, is of the appropriate type,
// and is non-zero. Coerces float64 to int because JSON and Javascript kinda suck.
func (c Config) Int(key string) (int, bool) {
i, ok := c[key]
if !ok {
return 0, false
}
var integer int
switch t := i.(type) {
case int:
integer = t
case float64:
integer = int(t)
case string:
var err error
integer, err = strconv.Atoi(t)
if err != nil {
return 0, false
}
default:
return 0, false
}
if integer == 0 {
return 0, false
}
return integer, true
}
// DefaultInt retrieves a non-zero int or the default value provided.
func (c Config) DefaultInt(key string, def int) int {
i, ok := c.Int(key)
if !ok {
return def
}
return i
}
// StringSlice retrieves an string slice, the bool says if it exists, is of the appropriate type,
// is non-nil and non-zero length
func (c Config) StringSlice(key string) ([]string, bool) {
ss, ok := c[key]
if !ok {
return nil, false
}
var slice []string
if intfSlice, ok := ss.([]interface{}); ok {
for _, i := range intfSlice {
slice = append(slice, i.(string))
}
} else if stringSlice, ok := ss.([]string); ok {
slice = stringSlice
} else {
return nil, false
}
// Also detects nil
if len(slice) == 0 {
return nil, false
}
return slice, true
}
func (c Config) MustForeignKeys(key string) []ForeignKey {
rawValue, ok := c[key]
if !ok {
return nil
}
switch v := rawValue.(type) {
case nil:
return nil
case []ForeignKey:
return v
case []interface{}: // in case binary, config is pass to driver in json format, so this key will be []interface{}
fks := make([]ForeignKey, 0, len(v))
for _, item := range v {
fk, ok := item.(map[string]interface{})
if !ok {
panic(errors.Errorf("found item of foreign keys, but it was not a map[string]interface{} (%T)", v))
}
configFK := Config(fk)
fks = append(fks, ForeignKey{
Name: configFK.MustString("name"),
Table: configFK.MustString("table"),
Column: configFK.MustString("column"),
ForeignTable: configFK.MustString("foreign_table"),
ForeignColumn: configFK.MustString("foreign_column"),
})
}
return fks
default:
panic(errors.Errorf("found key %s in config, but it was invalid (%T)", key, v))
}
}
// DefaultEnv grabs a value from the environment or a default.
// This is shared by drivers to get config for testing.
func DefaultEnv(key, def string) string {
val := os.Getenv(key)
if len(val) == 0 {
val = def
}
return val
}
// TablesFromList takes a whitelist or blacklist and returns
// the table names.
func TablesFromList(list []string) []string {
if len(list) == 0 {
return nil
}
var tables []string
for _, i := range list {
splits := strings.Split(i, ".")
if len(splits) == 1 {
tables = append(tables, splits[0])
}
}
return tables
}
// ColumnsFromList takes a whitelist or blacklist and returns
// the columns for a given table.
func ColumnsFromList(list []string, tablename string) []string {
if len(list) == 0 {
return nil
}
var columns []string
for _, i := range list {
splits := strings.Split(i, ".")
if len(splits) != 2 {
continue
}
if splits[0] == tablename || splits[0] == "*" {
columns = append(columns, splits[1])
}
}
return columns
}
// CombineConfigAndDBForeignKeys takes foreign keys from both config and db, filter by tableName and
// deduplicate by column name. If a foreign key is found in both config and db, the one in config will be used.
func CombineConfigAndDBForeignKeys(configForeignKeys []ForeignKey, tableName string, dbForeignKeys []ForeignKey) []ForeignKey {
combinedForeignKeys := make([]ForeignKey, 0, len(configForeignKeys)+len(dbForeignKeys))
appearedColumns := make(map[string]bool)
fkNameCount := make(map[string]int)
// Detect Composite Foreign Keys in the database by counting how many times they appear.
// boiler doesn't support Composite FKs and should ignore those
for _, fk := range dbForeignKeys {
fkNameCount[fk.Name] += 1
}
for _, fk := range configForeignKeys {
// need check table name here cause configForeignKeys contains all foreign keys of all tables
if fk.Table != tableName {
continue
}
if appearedColumns[fk.Column] {
continue
}
combinedForeignKeys = append(combinedForeignKeys, fk)
appearedColumns[fk.Column] = true
}
for _, fk := range dbForeignKeys {
// no need check table here, because dbForeignKeys are already filtered by table name
if appearedColumns[fk.Column] {
continue
}
if fkNameCount[fk.Name] != 1 {
continue
}
combinedForeignKeys = append(combinedForeignKeys, fk)
appearedColumns[fk.Column] = true
}
return combinedForeignKeys
}