Skip to content
This repository has been archived by the owner on Dec 8, 2022. It is now read-only.

Commit

Permalink
feat(faker): add support for keeping struct property values (#64)
Browse files Browse the repository at this point in the history
* Use value instead of type for getValue method

* Rename omit set to keep

* Overwrite default value when keep is set

* Reset comments

* Add additional tests for unsupported tags and pointer
  • Loading branch information
sebastian-walter authored and bxcodec committed Mar 27, 2019
1 parent e77d6ef commit 342a24f
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 17 deletions.
93 changes: 78 additions & 15 deletions faker.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const (
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
tagName = "faker"
keep = "keep"
ID = "uuid_digit"
HyphenatedID = "uuid_hyphenated"
EmailTag = "email"
Expand Down Expand Up @@ -256,12 +257,12 @@ func FakeData(a interface{}) error {

rval := reflect.ValueOf(a)

finalValue, err := getValue(reflectType.Elem())
finalValue, err := getValue(a)
if err != nil {
return err
}

rval.Elem().Set(finalValue.Convert(reflectType.Elem()))
rval.Elem().Set(finalValue.Elem().Convert(reflectType.Elem()))
return nil
}

Expand Down Expand Up @@ -318,15 +319,28 @@ func AddProvider(tag string, provider TaggedFunction) error {
return nil
}

func getValue(t reflect.Type) (reflect.Value, error) {
func getValue(a interface{}) (reflect.Value, error) {
t := reflect.TypeOf(a)
if t == nil {
return reflect.Value{}, fmt.Errorf("interface{} not allowed")
}
k := t.Kind()

switch k {
case reflect.Ptr:
v := reflect.New(t.Elem())
val, err := getValue(t.Elem())
if err != nil {
return reflect.Value{}, err
var val reflect.Value
var err error
if a != reflect.Zero(reflect.TypeOf(a)).Interface() {
val, err = getValue(reflect.ValueOf(a).Elem().Interface())
if err != nil {
return reflect.Value{}, err
}
} else {
val, err = getValue(v.Elem().Interface())
if err != nil {
return reflect.Value{}, err
}
}
v.Elem().Set(val.Convert(t.Elem()))
return v, nil
Expand All @@ -342,20 +356,33 @@ func getValue(t reflect.Type) (reflect.Value, error) {
if !v.Field(i).CanSet() {
continue // to avoid panic to set on unexported field in struct
}
tag := t.Field(i).Tag.Get(tagName)
tags := decodeTags(t, i)

switch tag {
case "":
val, err := getValue(v.Field(i).Type())
switch {
case tags.keepOriginal:
zero, err := isZero(reflect.ValueOf(a).Field(i))
if err != nil {
return reflect.Value{}, err
}
if zero {
err := setDataWithTag(v.Field(i).Addr(), tags.fieldType)
if err != nil {
return reflect.Value{}, err
}
continue
}
v.Field(i).Set(reflect.ValueOf(a).Field(i))
case tags.fieldType == "":
val, err := getValue(v.Field(i).Interface())
if err != nil {
return reflect.Value{}, err
}
val = val.Convert(v.Field(i).Type())
v.Field(i).Set(val)
case SKIP:
case tags.fieldType == SKIP:
continue
default:
err := setDataWithTag(v.Field(i).Addr(), tag)
err := setDataWithTag(v.Field(i).Addr(), tags.fieldType)
if err != nil {
return reflect.Value{}, err
}
Expand All @@ -375,7 +402,7 @@ func getValue(t reflect.Type) (reflect.Value, error) {
}
v := reflect.MakeSlice(t, len, len)
for i := 0; i < v.Len(); i++ {
val, err := getValue(t.Elem())
val, err := getValue(v.Index(i).Interface())
if err != nil {
return reflect.Value{}, err
}
Expand Down Expand Up @@ -422,11 +449,14 @@ func getValue(t reflect.Type) (reflect.Value, error) {
}
v := reflect.MakeMap(t)
for i := 0; i < len; i++ {
key, err := getValue(t.Key())
keyInstance := reflect.New(t.Key()).Elem().Interface()
key, err := getValue(keyInstance)
if err != nil {
return reflect.Value{}, err
}
val, err := getValue(t.Elem())

valueInstance := reflect.New(t.Elem()).Elem().Interface()
val, err := getValue(valueInstance)
if err != nil {
return reflect.Value{}, err
}
Expand All @@ -440,6 +470,39 @@ func getValue(t reflect.Type) (reflect.Value, error) {

}

func isZero(field reflect.Value) (bool, error) {
for _, kind := range []reflect.Kind{reflect.Struct, reflect.Slice, reflect.Array, reflect.Map} {
if kind == field.Kind() {
return false, fmt.Errorf("keep not allowed on struct")
}
}
return reflect.Zero(field.Type()).Interface() == field.Interface(), nil
}

func decodeTags(typ reflect.Type, i int) structTag {
tags := strings.Split(typ.Field(i).Tag.Get(tagName), ",")

keepOriginal := false
res := make([]string, 0)
for _, tag := range tags {
if tag == keep {
keepOriginal = true
continue
}
res = append(res, tag)
}

return structTag{
fieldType: strings.Join(res, ","),
keepOriginal: keepOriginal,
}
}

type structTag struct {
fieldType string
keepOriginal bool
}

func setDataWithTag(v reflect.Value, tag string) error {

if v.Kind() != reflect.Ptr {
Expand Down
98 changes: 96 additions & 2 deletions faker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,9 @@ func TestUnsuportedMapStringInterface(t *testing.T) {
type Sample struct {
Map map[string]interface{}
}
sample := new(Sample)
var sample = new(Sample)
if err := FakeData(sample); err == nil {
t.Error("Expected Got Error. But got nil")
t.Error("Expected Error. But got nil")
}
}

Expand Down Expand Up @@ -819,3 +819,97 @@ func TestTagWithPointer(t *testing.T) {
t.Error("Expected filled but got emtpy")
}
}

func TestItOverwritesDefaultValueIfKeepIsSet(t *testing.T) {
type TestStruct struct {
Email string `json:"email,omitempty" faker:"email,keep"`
}

test := TestStruct{}

err := FakeData(&test)
if err != nil {
t.Error("expected not error, but got: ", err)
}

if test.Email == "" {
t.Error("expected filled but got empty")
}
}
func TestItKeepsStructPropertyWhenTagKeepIsSet(t *testing.T) {
type TestStruct struct {
FirstName string `json:"first_name,omitempty" faker:"first_name_male,keep"`
Email string `json:"email,omitempty" faker:"email,keep"`
}

firstName := "Heino van der Laien"
test := TestStruct{
FirstName: firstName,
}

err := FakeData(&test)
if err != nil {
t.Error("expected not error, but got: ", err)
}

if test.FirstName != firstName {
t.Fatalf("expected: %s, but got: %s", firstName, test.FirstName)
}

if test.Email == "" {
t.Error("expected filled but got empty")
}
}

func TestItThrowsAnErrorWhenKeepIsUsedOnIncomparableType(t *testing.T) {
type TypeStructWithStruct struct {
Struct struct{} `faker:"first_name_male,keep"`
}
type TypeStructWithMap struct {
Map map[string]string `faker:"first_name_male,keep"`
}
type TypeStructWithSlice struct {
Slice []string `faker:"first_name_male,keep"`
}
type TypeStructWithArray struct {
Array [4]string `faker:"first_name_male,keep"`
}

withStruct := TypeStructWithStruct{}
withMap := TypeStructWithMap{}
withSlice := TypeStructWithSlice{}
withArray := TypeStructWithArray{}

for _, item := range []interface{}{withArray,withStruct,withMap,withSlice} {
err := FakeData(&item)
if err == nil {
t.Errorf("expected error, but got nil")
}
}
}

func TestItThrowsAnErrorWhenPointerToInterfaceIsUsed(t *testing.T) {
type PtrToInterface struct {
Interface *interface{}
}

interfacePtr := PtrToInterface{}

err := FakeData(&interfacePtr)
if err == nil {
t.Errorf("expected error, but got nil")
}
}

func TestItThrowsAnErrorWhenZeroValueWithKeepAndUnsupportedTagIsUsed(t *testing.T) {
type String struct {
StringVal string `faker:"keep,unsupported"`
}

val := String{}

err := FakeData(&val)
if err == nil {
t.Errorf("expected error, but got nil")
}
}

0 comments on commit 342a24f

Please sign in to comment.