Skip to content

Commit

Permalink
feat: adds a changeable tag name (go-faker#27)
Browse files Browse the repository at this point in the history
Signed-off-by: hohmannr <raphael.hohmann@tuta.io>
  • Loading branch information
hohmannr committed Jun 14, 2023
1 parent 91fd09b commit 2b5072f
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 48 deletions.
145 changes: 145 additions & 0 deletions example_with_custom_tags_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package faker_test

import (
"fmt"

"github.com/go-faker/faker/v4"
"github.com/go-faker/faker/v4/pkg/options"
)

// SomeStructWithTags ...
type SomeStructWithACustomTagName struct {
Latitude float32 `custom:"lat"`
Longitude float32 `custom:"long"`
RealAddress faker.RealAddress `custom:"real_address"`
CreditCardNumber string `custom:"cc_number"`
CreditCardType string `custom:"cc_type"`
Email string `custom:"email"`
DomainName string `custom:"domain_name"`
IPV4 string `custom:"ipv4"`
IPV6 string `custom:"ipv6"`
Password string `custom:"password"`
Jwt string `custom:"jwt"`
PhoneNumber string `custom:"phone_number"`
MacAddress string `custom:"mac_address"`
URL string `custom:"url"`
UserName string `custom:"username"`
TollFreeNumber string `custom:"toll_free_number"`
E164PhoneNumber string `custom:"e_164_phone_number"`
TitleMale string `custom:"title_male"`
TitleFemale string `custom:"title_female"`
FirstName string `custom:"first_name"`
FirstNameMale string `custom:"first_name_male"`
FirstNameFemale string `custom:"first_name_female"`
LastName string `custom:"last_name"`
Name string `custom:"name"`
UnixTime int64 `custom:"unix_time"`
Date string `custom:"date"`
Time string `custom:"time"`
MonthName string `custom:"month_name"`
Year string `custom:"year"`
DayOfWeek string `custom:"day_of_week"`
DayOfMonth string `custom:"day_of_month"`
Timestamp string `custom:"timestamp"`
Century string `custom:"century"`
TimeZone string `custom:"timezone"`
TimePeriod string `custom:"time_period"`
Word string `custom:"word"`
Sentence string `custom:"sentence"`
Paragraph string `custom:"paragraph"`
Currency string `custom:"currency"`
Amount float64 `custom:"amount"`
AmountWithCurrency string `custom:"amount_with_currency"`
UUIDHypenated string `custom:"uuid_hyphenated"`
UUID string `custom:"uuid_digit"`
Skip string `custom:"-"`
PaymentMethod string `custom:"oneof: cc, paypal, check, money order"` // oneof will randomly pick one of the comma-separated values supplied in the tag
AccountID int `custom:"oneof: 15, 27, 61"` // use commas to separate the values for now. Future support for other separator characters may be added
Price32 float32 `custom:"oneof: 4.95, 9.99, 31997.97"`
Price64 float64 `custom:"oneof: 47463.9463525, 993747.95662529, 11131997.978767990"`
NumS64 int64 `custom:"oneof: 1, 2"`
NumS32 int32 `custom:"oneof: -3, 4"`
NumS16 int16 `custom:"oneof: -5, 6"`
NumS8 int8 `custom:"oneof: 7, -8"`
NumU64 uint64 `custom:"oneof: 9, 10"`
NumU32 uint32 `custom:"oneof: 11, 12"`
NumU16 uint16 `custom:"oneof: 13, 14"`
NumU8 uint8 `custom:"oneof: 15, 16"`
NumU uint `custom:"oneof: 17, 18"`
PtrNumU *uint `custom:"oneof: 19, 20"`
}

func Example_withTagsAndCustomTagName() {

a := SomeStructWithTags{}
// just set the custom tag name option
err := faker.FakeData(&a, options.WithTagName("custom"))
if err != nil {
fmt.Println(err)
}
fmt.Printf("%+v", a)
/*
Result:
{
Latitude: 81.12195
Longitude: -84.38158
RealAddress: {Address:107 Guaymas Place City:Davis State:CA PostalCode:95616 Coordinates:{Latitude:38.567048 Longitude:-121.746046}}
CreditCardType: American Express
CreditCardNumber: 373641309057568
Email: mJBJtbv@OSAaT.ru
DomainName: FWZcaRE.ru,
IPV4: 99.23.42.63
IPV6: 975c:fb2c:2133:fbdd:beda:282e:1e0a:ec7d
Password: dfJdyHGuVkHBgnHLQQgpINApynzexnRpgIKBpiIjpTPOmNyMFb
Jwt: HDMNSOKhEIYkPIuHcVjfCtHlKkaqLGrUEqjKVkgR.HDMNSOKhEIYkPIuHcVjfCtHlKkaqLGrUEqjKVkgR.HDMNSOKhEIYkPIuHcVjfCtHlKkaqLGrUEqjKVkgR
PhoneNumber: 792-153-4861
MacAddress: cd:65:e1:d4:76:c6
URL: https://www.oEuqqAY.org/QgqfOhd
UserName: lVxELHS
TollFreeNumber: (777) 831-964572
E164PhoneNumber: +724891571063
TitleMale: Mr.
TitleFemale: Queen
FirstName: Whitney
FirstNameMale: Kenny
FirstNameFemale: Jana
LastName: Rohan
Name: Miss Casandra Kiehn
UnixTime: 1197930901
Date: 1982-02-27
Time: 03:10:25
MonthName: February
Year: 1996
DayOfWeek: Sunday
DayOfMonth: 20
Timestamp: 1973-06-21 14:50:46
Century: IV
TimeZone: Canada/Eastern
TimePeriod: AM
Word: nesciunt
Sentence: Consequatur perferendis aut sit voluptatem accusantium.
Paragraph: Aut consequatur sit perferendis accusantium voluptatem. Accusantium perferendis consequatur voluptatem sit aut. Aut sit accusantium consequatur voluptatem perferendis. Perferendis voluptatem aut accusantium consequatur sit.
Currency: IRR,
Amount: 88.990000,
AmountWithCurrency: XBB 49257.100000,
UUIDHypenated: 8f8e4463-9560-4a38-9b0c-ef24481e4e27,
UUID: 90ea6479fd0e4940af741f0a87596b73,
PaymentMethod: paypal,
AccountID: 61,
Price32: 4.95,
Price64: 993747.95662529
NumS64: 1
NumS32: -3
NumS16: 5
NumS8: -8
NumU64: 9
NumU32: 11
NumU16: 13
NumU8: 15
NumU: 17
PtrNumU: 19
Skip:
}
*/

}
5 changes: 2 additions & 3 deletions faker.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ const (
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
maxRetry = 10000 // max number of retry for unique values
tagName = "faker"
keep = "keep"
unique = "unique"
ID = "uuid_digit"
Expand Down Expand Up @@ -443,7 +442,7 @@ func getFakedValue(a interface{}, opts *options.Options) (reflect.Value, error)
continue
}

tags := decodeTags(t, i)
tags := decodeTags(t, i, opts.TagName)
switch {
case tags.keepOriginal:
zero, err := isZero(reflect.ValueOf(a).Field(i))
Expand Down Expand Up @@ -606,7 +605,7 @@ func isZero(field reflect.Value) (bool, error) {
return reflect.Zero(field.Type()).Interface() == field.Interface(), nil
}

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

Expand Down
125 changes: 80 additions & 45 deletions faker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,51 +251,51 @@ type CStruct struct {
}

type TaggedStruct struct {
Latitude float32 `faker:"lat"`
Longitude float32 `faker:"long"`
CreditCardNumber string `faker:"cc_number"`
CreditCardType string `faker:"cc_type"`
Email string `faker:"email"`
DomainName string `faker:"domain_name"`
IPV4 string `faker:"ipv4"`
IPV6 string `faker:"ipv6"`
Password string `faker:"password"`
Jwt string `faker:"jwt"`
PhoneNumber string `faker:"phone_number"`
MacAddress string `faker:"mac_address"`
URL string `faker:"url"`
UserName string `faker:"username"`
TollFreeNumber string `faker:"toll_free_number"`
E164PhoneNumber string `faker:"e_164_phone_number"`
TitleMale string `faker:"title_male"`
TitleFemale string `faker:"title_female"`
FirstName string `faker:"first_name"`
FirstNameMale string `faker:"first_name_male"`
FirstNameFemale string `faker:"first_name_female"`
LastName string `faker:"last_name"`
Name string `faker:"name"`
ChineseFirstName string `faker:"chinese_first_name"`
ChineseLastName string `faker:"chinese_last_name"`
ChineseName string `faker:"chinese_name"`
UnixTime int64 `faker:"unix_time"`
Date string `faker:"date"`
Time string `faker:"time"`
MonthName string `faker:"month_name"`
Year string `faker:"year"`
DayOfWeek string `faker:"day_of_week"`
DayOfMonth string `faker:"day_of_month"`
Timestamp string `faker:"timestamp"`
Century string `faker:"century"`
TimeZone string `faker:"timezone"`
TimePeriod string `faker:"time_period"`
Word string `faker:"word"`
Sentence string `faker:"sentence"`
Paragraph string `faker:"paragraph"`
Currency string `faker:"currency"`
Amount float32 `faker:"amount"`
AmountWithCurrency string `faker:"amount_with_currency"`
ID string `faker:"uuid_digit"`
HyphenatedID string `faker:"uuid_hyphenated"`
Latitude float32 `faker:"lat" custom_tag_name:"lat" `
Longitude float32 `faker:"long" custom_tag_name:"long"`
CreditCardNumber string `faker:"cc_number" custom_tag_name:"cc_number"`
CreditCardType string `faker:"cc_type" custom_tag_name:"cc_type"`
Email string `faker:"email" custom_tag_name:"email"`
DomainName string `faker:"domain_name" custom_tag_name:"domain_name"`
IPV4 string `faker:"ipv4" custom_tag_name:"ipv4"`
IPV6 string `faker:"ipv6" custom_tag_name:"ipv6"`
Password string `faker:"password" custom_tag_name:"password"`
Jwt string `faker:"jwt" custom_tag_name:"jwt"`
PhoneNumber string `faker:"phone_number" custom_tag_name:"phone_number"`
MacAddress string `faker:"mac_address" custom_tag_name:"mac_address"`
URL string `faker:"url" custom_tag_name:"url"`
UserName string `faker:"username" custom_tag_name:"username"`
TollFreeNumber string `faker:"toll_free_number" custom_tag_name:"toll_free_number"`
E164PhoneNumber string `faker:"e_164_phone_number" custom_tag_name:"e_164_phone_number"`
TitleMale string `faker:"title_male" custom_tag_name:"title_male"`
TitleFemale string `faker:"title_female" custom_tag_name:"title_female"`
FirstName string `faker:"first_name" custom_tag_name:"first_name"`
FirstNameMale string `faker:"first_name_male" custom_tag_name:"first_name_male"`
FirstNameFemale string `faker:"first_name_female" custom_tag_name:"first_name_female"`
LastName string `faker:"last_name" custom_tag_name:"last_name"`
Name string `faker:"name" custom_tag_name:"name"`
ChineseFirstName string `faker:"chinese_first_name" custom_tag_name:"chinese_first_name"`
ChineseLastName string `faker:"chinese_last_name" custom_tag_name:"chinese_last_name"`
ChineseName string `faker:"chinese_name" custom_tag_name:"chinese_name"`
UnixTime int64 `faker:"unix_time" custom_tag_name:"unix_time"`
Date string `faker:"date" custom_tag_name:"date"`
Time string `faker:"time" custom_tag_name:"time"`
MonthName string `faker:"month_name" custom_tag_name:"month_name"`
Year string `faker:"year" custom_tag_name:"year"`
DayOfWeek string `faker:"day_of_week" custom_tag_name:"day_of_week"`
DayOfMonth string `faker:"day_of_month" custom_tag_name:"day_of_month"`
Timestamp string `faker:"timestamp" custom_tag_name:"timestamp"`
Century string `faker:"century" custom_tag_name:"century"`
TimeZone string `faker:"timezone" custom_tag_name:"timezone"`
TimePeriod string `faker:"time_period" custom_tag_name:"time_period"`
Word string `faker:"word" custom_tag_name:"word"`
Sentence string `faker:"sentence" custom_tag_name:"sentence"`
Paragraph string `faker:"paragraph" custom_tag_name:"paragraph"`
Currency string `faker:"currency" custom_tag_name:"currency"`
Amount float32 `faker:"amount" custom_tag_name:"amount"`
AmountWithCurrency string `faker:"amount_with_currency" custom_tag_name:"amount_with_currency"`
ID string `faker:"uuid_digit" custom_tag_name:"uuid_digit"`
HyphenatedID string `faker:"uuid_hyphenated" custom_tag_name:"uuid_hyphenated"`
}

func (t TaggedStruct) String() string {
Expand Down Expand Up @@ -2324,6 +2324,41 @@ func TestRandomMapSliceSize(t *testing.T) {
}
}

func TestWithTagName(t *testing.T) {
a := TaggedStruct{}
if err := FakeData(&a, options.WithTagName("custom_tag_name")); err != nil {
t.Error(err)
}

if err := assertAllStructFieldsNonZero(a); err != nil {
t.Error(err)
}
}

// assertAllStructFieldsNonZero asserts that the given struct s has all fields set to a non-zero value.
// s must be a struct or a pointer to a struct, the function panics otherwise.
func assertAllStructFieldsNonZero(s interface{}) error {
v := reflect.ValueOf(s)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}

if v.Kind() != reflect.Struct {
panic("passed interface is not a struct or a pointer to a struct")
}

for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
zero := reflect.Zero(field.Type())

if reflect.DeepEqual(field.Interface(), zero.Interface()) {
return fmt.Errorf("field '%s' has a zero value", v.Type().Field(i).Name)
}
}

return nil
}

func TestWithFieldsToIgnore(t *testing.T) {
a := AStruct{}
if err := FakeData(&a, options.WithFieldsToIgnore("Height", "Name")); err != nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/errors/generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ var (
ErrTagNotSupported = "Tag unsupported: %s"
ErrTagAlreadyExists = "Tag exists"
ErrTagDoesNotExist = "Tag does not exist"
ErrTagNameInvalid = "Tag name is invalid"
ErrMoreArguments = "Passed more arguments than is possible : (%d)"
ErrNotSupportedPointer = "Use sample:=new(%s)\n faker.FakeData(sample) instead"
ErrSmallerThanZero = "Size:%d is smaller than zero."
Expand Down
14 changes: 14 additions & 0 deletions pkg/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ type Options struct {
RandomIntegerBoundary *interfaces.RandomIntegerBoundary
// RandomFloatBoundary sets the boundary for random float value generation. Boundaries should comply with float values constraints (IEEE 754)
RandomFloatBoundary *interfaces.RandomFloatBoundary
// SetTagName sets the tag name that should be used
TagName string
}

// MaxDepthOption used for configuring the max depth of nested struct for faker
Expand Down Expand Up @@ -106,6 +108,7 @@ func DefaultOption() *Options {
ops.MaxGenerateStringRetries = 1000000 //default
ops.RandomIntegerBoundary = (*interfaces.RandomIntegerBoundary)(atomic.LoadPointer(&iBoundary))
ops.RandomFloatBoundary = &interfaces.DefaultFloatBoundary
ops.TagName = "faker"
return ops
}

Expand Down Expand Up @@ -236,6 +239,17 @@ func WithRandomFloatBoundaries(boundary interfaces.RandomFloatBoundary) OptionFu
}
}

// WithTagName sets the tag name to use. Default tag name is 'faker'.
func WithTagName(tagName string) OptionFunc {
if tagName == "" {
err := errors.New(fakerErrors.ErrTagDoesNotExist)
panic(err)
}
return func(oo *Options) {
oo.TagName = tagName
}
}

// SetGenerateUniqueValues allows to set the single fake data generator functions to generate unique data.
func SetGenerateUniqueValues(unique bool) {
generateUniqueValues.Store(unique)
Expand Down

0 comments on commit 2b5072f

Please sign in to comment.