forked from corestoreio/pkg
-
Notifications
You must be signed in to change notification settings - Fork 0
/
fake.go
808 lines (723 loc) · 24.1 KB
/
fake.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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
// package pseudo is the fake data generator, heavily inspired by
// forgery and ffaker Ruby gems.
//
// CoreStore: This package has been refactored to avoid package global variables
// and global settings which are an anti-pattern. In a multi store/language
// environment a package global language limits everything. Also the global PRNG
// got eliminate and reduces a mutex bottle neck. This package can also handle
// max_len values for generated data and supports embedded structs which
// implements sql.Scanner interface.
//
// Most data and methods are ported from forgery/ffaker Ruby gems.
//
// Currently english and russian languages are available.
//
// For the list of available methods please look at
// https://godoc.org/github.com/icrowley/fake.
//
// Fake embeds samples data files unless you call UseExternalData(true) in order
// to be able to work without external files dependencies when compiled, so, if
// you add new data files or make changes to existing ones don't forget to
// regenerate data.go file using github.com/mjibson/esc tool and esc -o data.go
// -pkg fake data command (or you can just use go generate command if you are
// using Go 1.4 or later).
package pseudo
import (
"encoding"
"io"
"math"
"os"
"path/filepath"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/corestoreio/errors"
"github.com/corestoreio/pkg/util/conv"
"github.com/oklog/ulid"
"golang.org/x/exp/rand"
)
// Reason for using this package: at the moment well maintained and does not include net/http.
// Run: $ go get -u github.com/shuLhan/go-bindata
//go:generate go-bindata -o bindata.go -pkg pseudo data/...
// Faker allows a type to implement a custom fake data generation. The argument
// fieldName contains the name of the current field for which random/fake data
// should be generated. The return argument hasFakeDataApplied can be set to
// true, if fake data gets generated for the current field. Setting
// hasFakeDataApplied to false, the fake data should be generated by this
// package.
type Faker interface {
Fake(fieldName string) (hasFakeDataApplied bool, err error)
}
// Supported tags
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
tagName = "faker"
tagMaxLenName = "max_len"
// Skip indicates a struct tag, that the field should be skipped.
Skip = "-"
DefaultMaxRecursionLevel = 10
)
// FakeFunc generates new specific fake values. The returned interface{} type
// can only contain primitive types and time.Time.
type FakeFunc func(maxLen int) interface{}
// Options applied for the Service type.
type Options struct {
Lang string
UseExternalData bool
EnFallback bool
TimeLocation *time.Location // defaults to UTC
// RespectValidField if enabled allows to observe the `Valid bool` field of
// a struct. Like sql.NullString, sqlNullInt64 or all null.* types. If Valid
// is false, the other fields will be reset to their repective default
// values. Reason: All fields of a struct are getting applied with fake
// data, if Valid is false and the field gets written to the e.g. DB and
// later compared.
RespectValidField bool
// DisabledFieldNameUse if enabled avoids the usage of the field name
// instead of the struct tag to find out which kind of random function is
// needed.
DisabledFieldNameUse bool
// MaxLenStringLimit defines the upper bound of the maximal length for a
// string. If not set, defaults to 512.
MaxLenStringLimit uint64
// MaxFloatDecimals limits the float generation to this amount of decimals.
// Useful for MySQL/MariaDB float column type.
MaxFloatDecimals int
// MaxRecursionLevel default see DefaultMaxRecursionLevel
MaxRecursionLevel int
}
const maxLenStringLimit = 512
type optionFn struct {
sortOrder int
fn func(*Service) error
}
// WithTagFakeFunc extends faker with a new tag (or field name) to generate fake
// data with a specified custom algorithm. It will overwrite a previous set
// function.
func WithTagFakeFunc(tag string, provider FakeFunc) optionFn {
return optionFn{
sortOrder: 10,
fn: func(s *Service) error {
s.mu.Lock()
defer s.mu.Unlock()
s.funcs[tag] = provider
return nil
},
}
}
// WithTagFakeFuncAlias sets a new alias. E.g. when WithTagFakeFunc("http",...)
// adds a function to generate HTTP links, then calling
// WithTagFakeFuncAlias("https","http","ftps","ftp") would say that https is an
// alias of the http function and ftps is an alias of ftp.
func WithTagFakeFuncAlias(aliasTag ...string) optionFn {
return optionFn{
sortOrder: 100,
fn: func(s *Service) error {
s.mu.Lock()
defer s.mu.Unlock()
for i := 0; i < len(aliasTag); i = i + 2 {
// alias name => original name
s.funcsAliases[aliasTag[i]] = aliasTag[i+1]
}
return nil
},
}
}
// Service provides a service to generate fake data.
type Service struct {
r *rand.Rand
o Options
id *uint64
ulidEntropy io.Reader
mu sync.RWMutex
langMapping map[string]map[string][]string // cat/subcat/lang/samples
funcs map[string]FakeFunc
funcsAliases map[string]string // alias name => original name
}
// MustNewService creates a new Service but panics on error.
func MustNewService(seed uint64, o *Options, opts ...optionFn) *Service {
s, err := NewService(seed, o, opts...)
if err != nil {
panic(err)
}
return s
}
// NewService creates a new fake service.
func NewService(seed uint64, o *Options, opts ...optionFn) (*Service, error) {
if seed == 0 {
seed = uint64(time.Now().UnixNano())
}
if o == nil {
o = &Options{
EnFallback: true,
}
}
if o.Lang == "" {
o.Lang = "en"
}
if o.TimeLocation == nil {
o.TimeLocation = time.UTC
}
if o.MaxLenStringLimit == 0 {
o.MaxLenStringLimit = maxLenStringLimit
}
if o.MaxRecursionLevel == 0 {
o.MaxRecursionLevel = DefaultMaxRecursionLevel
}
s := &Service{
langMapping: make(map[string]map[string][]string),
r: rand.New(&lockedSource{src: rand.NewSource(seed)}),
o: *o,
id: new(uint64),
ulidEntropy: ulid.Monotonic(rand.New(rand.NewSource(seed)), 0),
}
s.funcs = map[string]FakeFunc{
"id": func(maxLen int) interface{} { return s.ID() },
"uuid": func(maxLen int) interface{} { return s.UUID() },
"uuid_string": func(maxLen int) interface{} { return s.UUIDString() },
"ulid": func(maxLen int) interface{} { return s.ULID().String() },
"mac_address": func(maxLen int) interface{} { return s.MacAddress() },
"domain_name": func(maxLen int) interface{} { return s.DomainName() },
"username": func(maxLen int) interface{} { return s.UserName() },
"url": func(maxLen int) interface{} { return s.URL() },
"ipv4": func(maxLen int) interface{} { return s.IPv4() },
"ipv6": func(maxLen int) interface{} { return s.IPv6() },
"password": func(maxLen int) interface{} {
if maxLen > 64 {
maxLen = 64
}
return s.Password(8, maxLen, true, true, true)
},
"email": func(maxLen int) interface{} { return s.EmailAddress() },
"lat": func(maxLen int) interface{} { return s.Latitude() },
"long": func(maxLen int) interface{} { return s.Longitude() },
"cc_number": func(maxLen int) interface{} { return s.CreditCardNum("") },
"cc_type": func(maxLen int) interface{} { return s.CreditCardType() },
"phone_number": func(maxLen int) interface{} { return s.Phone() },
"male_first_name": func(maxLen int) interface{} { return s.MaleFirstName() },
"female_first_name": func(maxLen int) interface{} { return s.FemaleFirstName() },
"name": func(maxLen int) interface{} { return s.FullName() },
"last_name": func(maxLen int) interface{} { return s.LastName() },
"first_name": func(maxLen int) interface{} { return s.FirstName() },
"prefix": func(maxLen int) interface{} { return s.Prefix() },
"suffix": func(maxLen int) interface{} { return s.Suffix() },
"date": func(maxLen int) interface{} { return s.Date() },
"clock": func(maxLen int) interface{} { return s.Clock() },
"time": func(maxLen int) interface{} { return s.Time() },
"timestamp": func(maxLen int) interface{} { return s.TimeStamp() },
"dob": func(maxLen int) interface{} { return s.Dob18() },
"timezone": func(maxLen int) interface{} { return s.TimeZone() },
"unix_time": func(maxLen int) interface{} { return s.RandomUnixTime() },
"month_name": func(maxLen int) interface{} { return s.Month() },
"month": func(maxLen int) interface{} { return s.MonthNum() },
"year": func(maxLen int) interface{} {
ny := time.Now().Year()
y := s.Year(ny-30, ny+5)
return y
},
"week_day": func(maxLen int) interface{} { return s.WeekDay() },
"sentence": func(maxLen int) interface{} { return s.Sentence(maxLen) },
"paragraph": func(maxLen int) interface{} { return s.Paragraph(maxLen) },
"currency": func(maxLen int) interface{} { return s.Currency() },
"currency_code": func(maxLen int) interface{} { return s.CurrencyCode() },
"price": func(maxLen int) interface{} { return s.Price() },
"price_currency": func(maxLen int) interface{} { return s.PriceWithCurrency() },
"word": func(maxLen int) interface{} { return s.Word(maxLen) },
"city": func(maxLen int) interface{} { return s.City() },
"postcode": func(maxLen int) interface{} { return s.Zip() },
"street": func(maxLen int) interface{} { return s.StreetAddress() },
"company": func(maxLen int) interface{} { return s.CompanyLegal() },
"country": func(maxLen int) interface{} { return s.Country() },
"country_id": func(maxLen int) interface{} { return s.CountryISO2() },
"region": func(maxLen int) interface{} { return s.State() },
}
s.funcsAliases = map[string]string{
"firstname": "first_name",
"middlename": "first_name",
"lastname": "last_name",
"password_hash": "password",
"zip": "postcode",
"address": "street",
"increment_id": "ulid",
"telephone": "phone_number",
"fax": "phone_number",
}
sort.Slice(opts, func(i, j int) bool {
return opts[i].sortOrder < opts[j].sortOrder // ascending 0-9 sorting ;-)
})
for _, o := range opts {
if err := o.fn(s); err != nil {
return nil, errors.WithStack(err)
}
}
// validate that the alias target exists
for alias, target := range s.funcsAliases {
if _, ok := s.funcs[alias]; ok {
return nil, errors.AlreadyExists.Newf("[pseudo] Alias %q already exists as a fakeFunc", alias)
}
if _, ok := s.funcs[target]; !ok {
return nil, errors.NotImplemented.Newf("[pseudo] Alias %q has an undefined target %q", alias, target)
}
}
return s, nil
}
// GetLangs returns a slice of available languages
func (s *Service) GetLangs() []string {
lng, _ := AssetDir("data")
return lng
}
// SetLang sets the language in which the data should be generated
// returns error if passed language is not available
func (s *Service) SetLang(newLang string) error {
found := false
for _, l := range s.GetLangs() {
if newLang == l {
found = true
s.o.Lang = newLang
break
}
}
if !found {
return errors.NotFound.Newf("[pseudo] The language passed (%s) is not available", newLang)
}
return nil
}
func join(parts ...string) string {
var filtered []string
for _, part := range parts {
if part != "" {
filtered = append(filtered, part)
}
}
return strings.Join(filtered, " ")
}
func (s *Service) generate(lang, cat string, fallback bool) string {
format := s.lookup(lang, cat+"_format", fallback)
var result string
for _, ru := range format {
if ru != '#' {
result += string(ru)
} else {
result += strconv.Itoa(s.r.Intn(10))
}
}
return result
}
func (s *Service) lookup(lang, cat string, fallback bool) string {
s.mu.Lock()
defer s.mu.Unlock()
return s._lookup(lang, cat, fallback)
}
func (s *Service) _lookup(lang, cat string, fallback bool) string {
if samples, ok := s.langMapping[lang][cat]; ok {
return samples[s.r.Intn(len(samples))]
}
samples, err := s.populateSamples(lang, cat)
if err != nil {
if pe, ok := err.(*os.PathError); lang != "en" && fallback && s.o.EnFallback && ok && pe.Err == os.ErrNotExist {
return s._lookup("en", cat, false)
}
return ""
}
return samples[s.r.Intn(len(samples))]
}
func (s *Service) populateSamples(lang, cat string) ([]string, error) {
data, err := s.readFile(lang, cat)
if err != nil {
return nil, err
}
if _, ok := s.langMapping[lang]; !ok {
s.langMapping[lang] = make(map[string][]string)
}
samples := strings.Split(strings.TrimSpace(string(data)), "\n")
s.langMapping[lang][cat] = samples
return samples, nil
}
func (s *Service) readFile(lang, cat string) (_ []byte, err error) {
fullPath := filepath.Join("data", lang, cat+".txt")
data, err := Asset(fullPath)
return data, err
}
// FakeData is the main function. Will generate a fake data based on your
// struct. You can use this for automation testing, or anything that need
// automated data. You don't need to Create your own data for your testing.
// Unsupported types are getting ignored.
func (s *Service) FakeData(ptr interface{}) error {
reflectType := reflect.TypeOf(ptr)
if ptr == nil || reflectType.Kind() != reflect.Ptr || reflect.ValueOf(ptr).IsNil() {
return errors.NotSupported.Newf("[pseudo] Nil/Non-pointer values are not supported. Argument ptr should be a pointer.")
}
finalValue, err := s.getValue(reflectType.Elem(), 0, 0)
if err != nil {
return errors.WithStack(err)
}
rVal := reflect.ValueOf(ptr)
rVal.Elem().Set(finalValue.Convert(reflectType.Elem()))
return nil
}
type scanner interface {
Scan(interface{}) error
}
func (s *Service) getValue(t reflect.Type, maxLen uint64, recursionLevel int) (rVal reflect.Value, err error) {
k := t.Kind()
if maxLen == 0 {
maxLen = math.MaxInt8
}
switch k {
case reflect.Ptr:
if recursionLevel < s.o.MaxRecursionLevel {
v := reflect.New(t.Elem())
val, err := s.getValue(t.Elem(), maxLen, recursionLevel+1)
if err != nil {
return rVal, err
}
v.Elem().Set(val.Convert(t.Elem()))
return v, nil
}
case reflect.Struct:
switch ts := t.String(); ts {
case "time.Time":
var neg int64 = 1
if s.r.Int()%2 == 0 {
neg = -1
}
ft := time.Now().Add(time.Second * time.Duration(neg*s.r.Int63n(3600*24*90)))
// proper way to get rid of the nano seconds
ft = time.Unix(ft.Unix(), 0).In(s.o.TimeLocation)
return reflect.ValueOf(ft), nil
default:
v := reflect.New(t).Elem()
var fkr Faker
if v.CanInterface() {
// The _ is important to not cause a panic: "comma ok" pattern with ignored ok
fkr, _ = v.Addr().Interface().(Faker)
}
shouldResetField := false
typeName := v.Type().String()
for i := 0; i < v.NumField(); i++ {
vf := v.Field(i) // value field
tf := t.Field(i) // type field
if !vf.CanSet() {
continue // to avoid panic to set on unexported field in struct
}
tag := tf.Tag.Get(tagName)
if tag == Skip {
continue
}
if maxLenTag := tf.Tag.Get(tagMaxLenName); maxLenTag != "" {
maxLen, err = strconv.ParseUint(maxLenTag, 10, 64)
if err != nil {
return rVal, err
}
}
// Custom functions must have precedence before interface Faker
// implementation of a type.
if vf.CanInterface() && vf.CanAddr() {
sTag := tag
if sTag == "" {
sTag = toSnakeCase(tf.Name)
}
if iFaceVal, ok := s.getFuncsValue(typeName, tf.Name, sTag, maxLen); ok {
switch tFace := vf.Addr().Interface().(type) {
case *time.Time:
iFaceValTime, err := conv.ToTimeE(iFaceVal)
if err != nil {
return rVal, errors.WithStack(err)
}
vf.Set(reflect.ValueOf(iFaceValTime))
continue
case scanner:
if err := tFace.Scan(iFaceVal); err != nil {
return rVal, errors.WithStack(err)
}
continue
case encoding.TextUnmarshaler:
bt, err := conv.ToByteE(iFaceVal)
if err != nil {
return rVal, errors.WithStack(err)
}
if err := tFace.UnmarshalText(bt); err != nil {
return rVal, errors.WithStack(err)
}
continue
case *string:
cv := conv.ToString(iFaceVal)
vf.Set(reflect.ValueOf(cv))
continue
default:
val := reflect.ValueOf(iFaceVal)
if isConvertibleType(vf.Kind()) && isConvertibleType(val.Kind()) {
val = val.Convert(vf.Type())
vf.Set(val)
}
continue
}
}
}
if fkr != nil {
// fieldName is the original field name as written in the struct.
if skip, err := fkr.Fake(tf.Name); err != nil {
return rVal, err
} else if skip {
continue
}
}
switch {
case tag == "":
// The check of isConvertibleType protects from a panic of
// Convert. Especially useful when an exported field has a
// func or interface or channel type.
if recursionLevel < s.o.MaxRecursionLevel {
val, err := s.getValue(vf.Type(), maxLen, recursionLevel+1)
if err != nil {
return reflect.Value{}, err
}
if isConvertibleType(vf.Kind()) && val.IsValid() {
val = val.Convert(vf.Type())
}
if val.IsValid() {
vf.Set(val)
}
}
default:
err := s.setDataWithTag(vf.Addr(), tag, maxLen, false)
if err != nil {
return reflect.Value{}, err
}
}
if !shouldResetField && t.Field(i).Name == "Valid" && vf.Kind() == reflect.Bool && !vf.Bool() {
shouldResetField = true
}
}
if shouldResetField {
v.Set(reflect.New(t).Elem())
}
return v, nil
}
case reflect.String:
ml := maxLen
if ml > s.o.MaxLenStringLimit {
ml = s.o.MaxLenStringLimit
}
res := s.randomString(ml)
return reflect.ValueOf(res), nil
case reflect.Array, reflect.Slice:
if recursionLevel < s.o.MaxRecursionLevel {
ml := maxLen
if ml > s.o.MaxLenStringLimit {
ml = s.o.MaxLenStringLimit
}
slLen := s.r.Uint64n(ml)
v := reflect.MakeSlice(t, int(slLen), int(slLen))
for i := 0; i < v.Len(); i++ {
val, err := s.getValue(t.Elem(), ml, recursionLevel+1)
if err != nil {
return rVal, err
}
v.Index(i).Set(val)
}
return v, nil
}
case reflect.Int:
return reflect.ValueOf(int(s.r.Uint64n(maxLen))), nil
case reflect.Int8:
return reflect.ValueOf(int8(s.r.Uint64n(maxLen))), nil
case reflect.Int16:
return reflect.ValueOf(int16(s.r.Uint64n(maxLen))), nil
case reflect.Int32:
return reflect.ValueOf(int32(s.r.Uint64n(maxLen))), nil
case reflect.Int64:
return reflect.ValueOf(int64(s.r.Uint64n(maxLen))), nil
case reflect.Float32:
return reflect.ValueOf(float32(s.r.Int31()%100) + s.r.Float32()), nil
case reflect.Float64:
m := int32(math.Pow10(int(s.r.Int31n(4))))
f := float64(s.r.Int31()%m) + s.r.Float64()
if s.o.MaxFloatDecimals > 0 {
p10 := math.Pow10(s.o.MaxFloatDecimals)
f = math.Round(f*p10) / p10
}
return reflect.ValueOf(f), nil
case reflect.Bool:
val := s.r.Uint64n(3) > 0 // create more true values
return reflect.ValueOf(val), nil
case reflect.Uint:
return reflect.ValueOf(uint(s.r.Uint64n(maxLen))), nil
case reflect.Uint8:
return reflect.ValueOf(uint8(s.r.Uint64n(maxLen))), nil
case reflect.Uint16:
return reflect.ValueOf(uint16(s.r.Uint64n(maxLen))), nil
case reflect.Uint32:
return reflect.ValueOf(uint32(s.r.Uint64n(maxLen))), nil
case reflect.Uint64:
return reflect.ValueOf(uint64(s.r.Uint64n(maxLen))), nil
case reflect.Map:
if recursionLevel < s.o.MaxRecursionLevel {
v := reflect.MakeMap(t)
randLen := s.r.Uint64n(maxLen)
var i uint64
for ; i < randLen; i++ {
key, err := s.getValue(t.Key(), maxLen, recursionLevel+1)
if err != nil {
return rVal, err
}
val, err := s.getValue(t.Elem(), maxLen, recursionLevel+1)
if err != nil {
return rVal, err
}
v.SetMapIndex(key, val)
}
return v, nil
}
case reflect.Func, reflect.Chan, reflect.Interface, reflect.Complex64, reflect.Complex128:
// ignore
return rVal, nil
}
return rVal, nil // ignore any other type
}
func isConvertibleType(k reflect.Kind) bool {
switch k {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return true
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return true
case reflect.Float32, reflect.Float64:
return true
case reflect.Complex64, reflect.Complex128:
return true
case reflect.String:
return true
case reflect.Slice:
return true
}
return false
}
func (s *Service) getFuncsValue(typeName, fieldName, tag string, maxLen uint64) (interface{}, bool) {
// fmt.Printf("%s.%s Tag:%s\n", typeName, fieldName, tag)
if fn, ok := s.funcs[typeName+"."+fieldName]; ok {
iFaceVal := fn(int(maxLen))
return iFaceVal, true
}
// lookup an alias for parent_id
if fnAlias, ok := s.funcsAliases[tag]; ok && fnAlias != "" {
tag = fnAlias
}
fn, ok := s.funcs[tag]
if !ok {
return nil, false
}
iFaceVal := fn(int(maxLen))
return iFaceVal, true
}
func (s *Service) setDataWithTag(v reflect.Value, tag string, maxLen uint64, tagIsFieldName bool) error {
if v.Kind() != reflect.Ptr {
return errors.NotSupported.Newf("[pseudo] Non-pointer values are not supported. Argument ptr should be a pointer.")
}
// TODO check if the map access causes a race condition.
if fnAlias, ok := s.funcsAliases[tag]; ok && fnAlias != "" {
tag = fnAlias
}
fn, ok := s.funcs[tag]
if !ok && tagIsFieldName {
return nil
}
if !ok && !tagIsFieldName {
return errors.NotFound.Newf("[pseudo] Tag %q not found in map", tag)
}
iFaceVal := fn(int(maxLen))
v = reflect.Indirect(v)
switch k := v.Kind(); k {
case reflect.Float32, reflect.Float64:
val, err := conv.ToFloat64E(iFaceVal)
if err != nil {
return errors.WithStack(err)
}
v.SetFloat(val)
case reflect.Bool:
val, err := conv.ToBoolE(iFaceVal)
if err != nil {
return errors.WithStack(err)
}
v.SetBool(val)
case reflect.String:
val, err := conv.ToStringE(iFaceVal)
if err != nil {
return errors.WithStack(err)
}
v.SetString(val)
case reflect.Slice: // TODO must be improved to detect []byte
val, err := conv.ToByteE(iFaceVal)
if err != nil {
return errors.WithStack(err)
}
v.SetBytes(val)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val, err := conv.ToInt64E(iFaceVal)
if err != nil {
return errors.Wrapf(err, "[pseudo] For Tag %q", tag)
}
v.SetInt(val)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val, err := conv.ToUintE(iFaceVal)
if err != nil {
return errors.WithStack(err)
}
v.SetUint(uint64(val))
default:
return errors.NotSupported.Newf("[pseudo] Kind %q not supported in setDataWithTag", k)
}
return nil
}
func (s *Service) randomString(n uint64) string {
b := make([]byte, n)
for i, cache, remain := int64(n-1), s.r.Uint64(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = s.r.Uint64(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
func (s *Service) randomElementFromSliceString(sl []string) string {
return sl[s.r.Int()%len(sl)]
}
type caseCache struct {
mu sync.RWMutex
cache map[string]string
}
var (
matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
caseCacheService = &caseCache{cache: map[string]string{
"CountryID": "country_id",
"EntityID": "entity_id",
"PasswordHash": "password_hash",
}}
)
func toSnakeCase(str string) string {
caseCacheService.mu.RLock()
caStr := caseCacheService.cache[str]
caseCacheService.mu.RUnlock()
if caStr != "" {
return caStr
}
caseCacheService.mu.Lock()
defer caseCacheService.mu.Unlock()
snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}")
snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
lc := strings.ToLower(snake)
caseCacheService.cache[str] = lc
return lc
}