From 6f20dff3e4654f30a4effa0e210c96d683989f66 Mon Sep 17 00:00:00 2001 From: Iman Tumorang Date: Tue, 19 Sep 2023 16:40:12 +0000 Subject: [PATCH 1/4] chore: fix the ignore interface --- faker.go | 159 +++++++++++++++++++++++++++++++------------------- faker_test.go | 55 +++++++++++++++++ go.mod | 2 +- go.sum | 1 - 4 files changed, 156 insertions(+), 61 deletions(-) diff --git a/faker.go b/faker.go index 7a7758d..048d499 100644 --- a/faker.go +++ b/faker.go @@ -313,52 +313,59 @@ func FakeData(a interface{}, opt ...options.OptionFunc) error { return err } - rval.Elem().Set(finalValue.Elem().Convert(reflectType.Elem())) + if rval.Elem().CanSet() && rval.Elem(). + CanConvert(reflectType.Elem()) { + rval.Elem().Set(finalValue.Elem(). + Convert(reflectType.Elem())) + } return nil } // AddProvider extend faker with tag to generate fake data with specified custom algorithm // Example: -// type Gondoruwo struct { -// Name string -// Locatadata int -// } // -// type Sample struct { -// ID int64 `faker:"customIdFaker"` -// Gondoruwo Gondoruwo `faker:"gondoruwo"` -// Danger string `faker:"danger"` -// } +// type Gondoruwo struct { +// Name string +// Locatadata int +// } +// +// type Sample struct { +// ID int64 `faker:"customIdFaker"` +// Gondoruwo Gondoruwo `faker:"gondoruwo"` +// Danger string `faker:"danger"` +// } // -// func CustomGenerator() { -// // explicit -// faker.AddProvider("customIdFaker", func(v reflect.Value) (interface{}, error) { -// return int64(43), nil -// }) -// // functional -// faker.AddProvider("danger", func() faker.TaggedFunction { -// return func(v reflect.Value) (interface{}, error) { -// return "danger-ranger", nil -// } -// }()) -// faker.AddProvider("gondoruwo", func(v reflect.Value) (interface{}, error) { -// obj := Gondoruwo{ -// Name: "Power", -// Locatadata: 324, -// } -// return obj, nil -// }) -// } +// func CustomGenerator() { +// // explicit +// faker.AddProvider("customIdFaker", func(v reflect.Value) (interface{}, error) { +// return int64(43), nil +// }) +// // functional +// faker.AddProvider("danger", func() faker.TaggedFunction { +// return func(v reflect.Value) (interface{}, error) { +// return "danger-ranger", nil +// } +// }()) +// faker.AddProvider("gondoruwo", func(v reflect.Value) (interface{}, error) { +// obj := Gondoruwo{ +// Name: "Power", +// Locatadata: 324, +// } +// return obj, nil +// }) +// } // -// func main() { -// CustomGenerator() -// var sample Sample -// faker.FakeData(&sample) -// fmt.Printf("%+v", sample) -// } +// func main() { +// CustomGenerator() +// var sample Sample +// faker.FakeData(&sample) +// fmt.Printf("%+v", sample) +// } // // Will print -// {ID:43 Gondoruwo:{Name:Power Locatadata:324} Danger:danger-ranger} +// +// {ID:43 Gondoruwo:{Name:Power Locatadata:324} Danger:danger-ranger} +// // Notes: when using a custom provider make sure to return the same type as the field func AddProvider(tag string, provider interfaces.TaggedFunction) error { if _, ok := mapperTag.Load(tag); ok { @@ -379,11 +386,11 @@ func RemoveProvider(tag string) error { return nil } -func getFakedValue(a interface{}, opts *options.Options) (reflect.Value, error) { - t := reflect.TypeOf(a) +func getFakedValue(item interface{}, opts *options.Options) (reflect.Value, error) { + t := reflect.TypeOf(item) if t == nil { if opts.IgnoreInterface { - return reflect.New(reflect.TypeOf(reflect.Struct)), nil + return reflect.ValueOf(nil), nil } return reflect.Value{}, fmt.Errorf("interface{} not allowed") } @@ -395,21 +402,28 @@ func getFakedValue(a interface{}, opts *options.Options) (reflect.Value, error) opts.MaxDepthOption.ForgetType(t) }() k := t.Kind() - switch k { case reflect.Ptr: v := reflect.New(t.Elem()) var val reflect.Value var err error - if a != reflect.Zero(reflect.TypeOf(a)).Interface() { - val, err = getFakedValue(reflect.ValueOf(a).Elem().Interface(), opts) + if item != reflect.Zero(reflect.TypeOf(item)).Interface() { + val, err = getFakedValue(reflect.ValueOf(item).Elem().Interface(), opts) + } else { val, err = getFakedValue(v.Elem().Interface(), opts) } if err != nil { return reflect.Value{}, err } - v.Elem().Set(val.Convert(t.Elem())) + + if reflect.ValueOf(val).IsZero() { + return v, nil + } + + if v.Elem().CanSet() && v.Elem().CanConvert(t.Elem()) { + v.Elem().Set(val.Convert(t.Elem())) + } return v, nil case reflect.Struct: switch t.String() { @@ -417,7 +431,7 @@ func getFakedValue(a interface{}, opts *options.Options) (reflect.Value, error) ft := time.Now().Add(time.Duration(rand.Int63())) return reflect.ValueOf(ft), nil default: - originalDataVal := reflect.ValueOf(a) + originalDataVal := reflect.ValueOf(item) v := reflect.New(t).Elem() retry := 0 // error if cannot generate unique value after maxRetry tries for i := 0; i < v.NumField(); i++ { @@ -441,7 +455,7 @@ func getFakedValue(a interface{}, opts *options.Options) (reflect.Value, error) tags := decodeTags(t, i) switch { case tags.keepOriginal: - zero, err := isZero(reflect.ValueOf(a).Field(i)) + zero, err := isZero(reflect.ValueOf(item).Field(i)) if err != nil { return reflect.Value{}, err } @@ -452,14 +466,19 @@ func getFakedValue(a interface{}, opts *options.Options) (reflect.Value, error) } continue } - v.Field(i).Set(reflect.ValueOf(a).Field(i)) + v.Field(i).Set(reflect.ValueOf(item).Field(i)) case tags.fieldType == "": val, err := getFakedValue(v.Field(i).Interface(), opts) if err != nil { return reflect.Value{}, err } - val = val.Convert(v.Field(i).Type()) - v.Field(i).Set(val) + + if v.Field(i).CanSet() { + if !reflect.ValueOf(val).IsZero() && val.CanConvert(v.Field(i).Type()) { + val = val.Convert(v.Field(i).Type()) + v.Field(i).Set(val) + } + } case tags.fieldType == SKIP: item := originalDataVal.Field(i).Interface() if v.CanSet() && item != nil { @@ -473,9 +492,8 @@ func getFakedValue(a interface{}, opts *options.Options) (reflect.Value, error) } if tags.unique { - if retry >= maxRetry { - return reflect.Value{}, fmt.Errorf(fakerErrors.ErrUniqueFailure, reflect.TypeOf(a).Field(i).Name) + return reflect.Value{}, fmt.Errorf(fakerErrors.ErrUniqueFailure, reflect.TypeOf(item).Field(i).Name) } value := v.Field(i).Interface() @@ -498,18 +516,26 @@ func getFakedValue(a interface{}, opts *options.Options) (reflect.Value, error) res, err := randomString(opts.RandomStringLength, *opts) return reflect.ValueOf(res), err case reflect.Slice: + length := randomSliceAndMapSize(*opts) if opts.SetSliceMapNilIfLenZero && length == 0 { return reflect.Zero(t), nil } v := reflect.MakeSlice(t, length, length) - for i := 0; i < v.Len(); i++ { + for i := 0; i < length; i++ { val, err := getFakedValue(v.Index(i).Interface(), opts) if err != nil { return reflect.Value{}, err } - val = val.Convert(v.Index(i).Type()) - v.Index(i).Set(val) + // if the value generated is NIL/Zero + // it will kept it as nil + if reflect.ValueOf(val).IsZero() { + continue + } + if val.CanConvert(v.Index(i).Type()) { + val = val.Convert(v.Index(i).Type()) + v.Index(i).Set(val) + } } return v, nil case reflect.Array: @@ -519,7 +545,12 @@ func getFakedValue(a interface{}, opts *options.Options) (reflect.Value, error) if err != nil { return reflect.Value{}, err } - val = val.Convert(v.Index(i).Type()) + if reflect.ValueOf(val).IsZero() { + continue + } + if val.CanConvert(v.Index(i).Type()) { + val = val.Convert(v.Index(i).Type()) + } v.Index(i).Set(val) } return v, nil @@ -574,6 +605,13 @@ func getFakedValue(a interface{}, opts *options.Options) (reflect.Value, error) if err != nil { return reflect.Value{}, err } + + keyIsZero := reflect.ValueOf(key).IsZero() + valIsZero := reflect.ValueOf(val).IsZero() + + if keyIsZero || valIsZero { + continue + } key = key.Convert(t.Key()) val = val.Convert(v.Type().Elem()) v.SetMapIndex(key, val) @@ -903,11 +941,13 @@ func userDefinedNumber(v reflect.Value, tag string) error { return fmt.Errorf(fakerErrors.ErrTagNotSupported, tag) } - v.Set(reflect.ValueOf(res).Convert(v.Type())) + if v.CanSet() && v.CanConvert(v.Type()) { + v.Set(reflect.ValueOf(res).Convert(v.Type())) + } return nil } -//extractSliceLengthFromTag checks if the sliceLength tag 'slice_len' is set, if so, returns its value, else return a random length +// extractSliceLengthFromTag checks if the sliceLength tag 'slice_len' is set, if so, returns its value, else return a random length func extractSliceLengthFromTag(tag string, opt options.Options) (int, error) { if strings.Contains(tag, SliceLength) { lenParts := strings.SplitN(findSliceLenReg.FindString(tag), Equals, -1) @@ -1283,9 +1323,10 @@ func randomStringNumber(n int) string { // RandomInt Get three parameters , only first mandatory and the rest are optional // (minimum_int, maximum_int, count) -// If only set one parameter : An integer greater than minimum_int will be returned -// If only set two parameters : All integers between minimum_int and maximum_int will be returned, in a random order. -// If three parameters: `count` integers between minimum_int and maximum_int will be returned. +// +// If only set one parameter : An integer greater than minimum_int will be returned +// If only set two parameters : All integers between minimum_int and maximum_int will be returned, in a random order. +// If three parameters: `count` integers between minimum_int and maximum_int will be returned. func RandomInt(parameters ...int) (p []int, err error) { switch len(parameters) { case 1: diff --git a/faker_test.go b/faker_test.go index 726a278..b75b8e9 100644 --- a/faker_test.go +++ b/faker_test.go @@ -2456,3 +2456,58 @@ func TestFakeDate_ConcurrentSafe(t *testing.T) { } wg.Wait() } + +type StructWithInterfaceContainsMethod struct { + I Interface +} + +type Interface interface { + test() +} + +func TestNonEmptyInterface(t *testing.T) { + var s StructWithInterfaceContainsMethod + if err := FakeData(&s, options.WithIgnoreInterface(true)); err != nil { + t.Errorf("%+v", err) + t.FailNow() + } +} + +type InterfaceStruct struct { + A string + B map[string]any + C map[string]interface{} + D Interface + E interface{} + F map[interface{}]any + G map[any]any + H map[any]interface{} + I []interface{} + J [2]interface{} + K [][]interface{} + L [][2]interface{} + M [3][2]interface{} + N map[any][]any + O []int +} + +func TestWithInterfaceStruct(t *testing.T) { + var s InterfaceStruct + if err := FakeData(&s, options.WithIgnoreInterface(true)); err != nil { + t.Errorf("%+v", err) + t.FailNow() + } +} + +func TestWithInterfaceStructWithIgnoreFalse(t *testing.T) { + var s InterfaceStruct + err := FakeData(&s, options.WithIgnoreInterface(false)) + if err == nil { + t.Errorf("expect error: but got %v", err) + t.FailNow() + } + if err.Error() != "interface{} not allowed" { + t.Errorf("expect error: \"interface{} not allowed\" but got %v", err.Error()) + t.FailNow() + } +} diff --git a/go.mod b/go.mod index 21d0390..9405849 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module github.com/go-faker/faker/v4 -go 1.17 +go 1.18 require golang.org/x/text v0.3.7 diff --git a/go.sum b/go.sum index 2274b80..1f78e03 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,2 @@ golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From 4b7b55dd82977198fb328fadb9d120bd91917388 Mon Sep 17 00:00:00 2001 From: Iman Tumorang Date: Tue, 19 Sep 2023 16:44:00 +0000 Subject: [PATCH 2/4] chore: apply go format --- faker.go | 2 +- faker_test.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/faker.go b/faker.go index 576243b..394f238 100644 --- a/faker.go +++ b/faker.go @@ -316,7 +316,7 @@ func FakeData(a interface{}, opt ...options.OptionFunc) error { CanConvert(reflectType.Elem()) { rval.Elem().Set(finalValue.Elem(). Convert(reflectType.Elem())) - } + } return nil } diff --git a/faker_test.go b/faker_test.go index c4497b2..318313f 100644 --- a/faker_test.go +++ b/faker_test.go @@ -2493,7 +2493,6 @@ func TestFakeDate_ConcurrentSafe(t *testing.T) { } type StructWithInterfaceContainsMethod struct { - I Interface } @@ -2547,4 +2546,4 @@ func TestWithInterfaceStructWithIgnoreFalse(t *testing.T) { t.Errorf("expect error: \"interface{} not allowed\" but got %v", err.Error()) t.FailNow() } -} +} From 189defbff6888075cf74983db8c4bf3895b42d4f Mon Sep 17 00:00:00 2001 From: Iman Tumorang Date: Wed, 20 Sep 2023 09:28:01 +0000 Subject: [PATCH 3/4] chore: fix linter issue --- .golangci.yaml | 10 ++++++---- faker.go | 6 +++--- faker_test.go | 2 +- misc/makefile/tools.Makefile | 2 +- pkg/options/options.go | 2 +- random_source_test.go | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.golangci.yaml b/.golangci.yaml index 496d8f3..2ffa708 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -11,7 +11,7 @@ linters-settings: golint: min-confidence: 0.8 gocyclo: - min-complexity: 60 + min-complexity: 70 maligned: suggest-new: true dupl: @@ -27,7 +27,7 @@ linters: # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint disable-all: true enable: - - deadcode + # - deadcode - errcheck - goconst - gocyclo @@ -37,12 +37,12 @@ linters: - ineffassign - misspell - staticcheck - - structcheck + # - structcheck - typecheck - unconvert - unparam - unused - - varcheck + # - varcheck issues: exclude-rules: @@ -52,6 +52,8 @@ issues: - path: datetime.go linters: - misspell + - path: . + text: "parameter 'v' seems to be unused" run: timeout: 5m go: "1.17" diff --git a/faker.go b/faker.go index 394f238..d7a2a18 100644 --- a/faker.go +++ b/faker.go @@ -480,9 +480,9 @@ func getFakedValue(item interface{}, opts *options.Options) (reflect.Value, erro } case tags.fieldType == SKIP: - item := originalDataVal.Field(i).Interface() - if v.CanSet() && item != nil { - v.Field(i).Set(reflect.ValueOf(item)) + data := originalDataVal.Field(i).Interface() + if v.CanSet() && data != nil { + v.Field(i).Set(reflect.ValueOf(data)) } default: err := setDataWithTag(v.Field(i).Addr(), tags.fieldType, *opts) diff --git a/faker_test.go b/faker_test.go index 318313f..3e950a4 100644 --- a/faker_test.go +++ b/faker_test.go @@ -2477,7 +2477,7 @@ func TestFakeData_RecursiveType(t *testing.T) { } } -func TestFakeDate_ConcurrentSafe(t *testing.T) { +func TestFakeDate_ConcurrentSafe(_ *testing.T) { var wg sync.WaitGroup wg.Add(1000) fmt.Println("Running for loop…") diff --git a/misc/makefile/tools.Makefile b/misc/makefile/tools.Makefile index 2565418..eac564b 100644 --- a/misc/makefile/tools.Makefile +++ b/misc/makefile/tools.Makefile @@ -42,7 +42,7 @@ bin/tparse: bin GOLANGCI := $(shell command -v golangci-lint || echo "bin/golangci-lint") golangci-lint: bin/golangci-lint ## Installs golangci-lint (linter) -bin/golangci-lint: VERSION := 1.48.0 +bin/golangci-lint: VERSION := 1.49.0 bin/golangci-lint: GITHUB := golangci/golangci-lint bin/golangci-lint: ARCHIVE := golangci-lint-$(VERSION)-$(OSTYPE)-amd64.tar.gz bin/golangci-lint: bin diff --git a/pkg/options/options.go b/pkg/options/options.go index 45bd491..c029177 100644 --- a/pkg/options/options.go +++ b/pkg/options/options.go @@ -18,7 +18,7 @@ var ( randomStringLen int32 = 25 lang unsafe.Pointer randomMaxSize int32 = 100 - randomMinSize int32 = 0 + randomMinSize int32 iBoundary unsafe.Pointer ) diff --git a/random_source_test.go b/random_source_test.go index ed03026..6a94451 100644 --- a/random_source_test.go +++ b/random_source_test.go @@ -8,7 +8,7 @@ import ( "time" ) -func TestSetRandomSource(t *testing.T) { +func TestSetRandomSource(_ *testing.T) { SetRandomSource(NewSafeSource(mathrand.NewSource(time.Now().UnixNano()))) _ = rand.Int31n(100) From 75b5cb5c69286a8927ffe4a09a15b2460c32fc88 Mon Sep 17 00:00:00 2001 From: Iman Tumorang Date: Wed, 20 Sep 2023 09:29:57 +0000 Subject: [PATCH 4/4] chore: fix the gh action --- .github/workflows/go.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 0f80446..4d77c25 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.17 + go-version: 1.18 - name: Linter run: make lint-prepare && make lint @@ -31,7 +31,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.17 + go-version: 1.18 - name: Test run: go test -v ./... -gcflags=all=-l -coverprofile=coverage.txt -covermode=atomic -race