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

feat: Implement oneof tag #103

Merged
merged 18 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5e07ae6
move custom tag check to inside userDefinedArray (following similar p…
andrew-werdna Jun 6, 2020
8b03abb
go fmt fixes; add support for 'oneof' tag for strings; add one basic …
andrew-werdna Jun 6, 2020
86c668c
add error check and error return for malformed tag; add extra tests t…
andrew-werdna Jun 6, 2020
61e4b06
added support for a tag called oneof for strings and ints
andrew-werdna Jun 6, 2020
48bc5e7
update the creation of the custom types (not using the underlying types)
andrew-werdna Jun 7, 2020
6601d1c
update examples; add const for convenience
andrew-werdna Jun 7, 2020
e6329a2
go fmt
andrew-werdna Jun 7, 2020
0267e71
update documentation examples; add TrimSpace to ensure space between …
andrew-werdna Jun 7, 2020
2c3d65f
update readme with caveat about 'oneof' tag
andrew-werdna Jun 7, 2020
c35890a
linter fixes
andrew-werdna Jun 7, 2020
1a9fe1f
clean up duplicate if conditions
andrew-werdna Jun 7, 2020
668e63e
Merge remote-tracking branch 'origin/master' into setup-OneOf-tag
andrew-werdna Jun 7, 2020
cca5c35
finish fixing merge conflicts
andrew-werdna Jun 7, 2020
cfb8004
update example to be correct. use ':' to separate arguments not ','
andrew-werdna Jun 7, 2020
210f21c
update example comments for clarity
andrew-werdna Jun 7, 2020
0c1fccd
ensure comma separated arguments to oneof tag; add several more tests…
andrew-werdna Jun 7, 2020
fbde531
update documentation examples
andrew-werdna Jun 7, 2020
4c44fc2
remove useless if statement that is never true
andrew-werdna Jun 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Faker will generate you a fake data based on your Struct.
[![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/bxcodec/faker/blob/master/LICENSE)
[![GoDoc](https://godoc.org/github.com/bxcodec/faker?status.svg)](https://godoc.org/github.com/bxcodec/faker)
[![Go.Dev](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/bxcodec/faker/v3?tab=doc)

## Index

* [Support](#support)
Expand All @@ -38,16 +38,16 @@ go get -u github.com/bxcodec/faker/v3
# Example

---
- Using Struct's tag:

- Using Struct's tag:
- [basic tags: example_with_tags_test.go](/example_with_tags_test.go)
- [length and bounds: example_with_tags_lenbounds_test.go](/example_with_tags_lenbounds_test.go)
- [language: example_with_tags_lang_test.go](/example_with_tags_lang_test.go)
- [unique: example_with_tags_unique_test.go](example_with_tags_unique_test.go)
- Custom Struct's tag (define your own faker data): [example_custom_faker_test.go](/example_custom_faker_test.go)
- Without struct's tag: [example_without_tag_test.go](/example_without_tag_test.go)
- Single Fake Data Function: [example_single_fake_data_test.go](/example_single_fake_data_test.go)

## DEMO

---
Expand Down Expand Up @@ -94,6 +94,7 @@ Unfortunately this library has some limitation
* It does not support the `map[interface{}]interface{}`, `map[any_type]interface{}` & `map[interface{}]any_type` data types. Once again, we cannot generate values for an unknown data type.
* Custom types are not fully supported. However some custom types are already supported: we are still investigating how to do this the correct way. For now, if you use `faker`, it's safer not to use any custom types in order to avoid panics.
* Some extra custom types can be supported IF AND ONLY IF extended with [AddProvider()](https://github.com/bxcodec/faker/blob/9169c33ae9926e5b8f8732909790ee20b10b736a/faker.go#L320) please see [example](example_custom_faker_test.go#L46)
* The `oneof` tag currently only supports `string` & `int`. Further support is coming soon. See [example](example_with_tags_test.go#L53) for usage.

## Contribution

Expand Down
2 changes: 1 addition & 1 deletion example_custom_faker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func CustomGenerator() {
})

_ = faker.AddProvider("customUUID", func(v reflect.Value) (interface{}, error) {
s := []byte{
s := CustomUUID{
0, 8, 7, 2, 3,
}
return s, nil
Expand Down
4 changes: 4 additions & 0 deletions example_with_tags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ type SomeStructWithTags struct {
UUIDHypenated string `faker:"uuid_hyphenated"`
UUID string `faker:"uuid_digit"`
Skip string `faker:"-"`
PaymentMethod string `faker:"oneof: cc, paypal, check, money order"` // oneof will randomly pick one of the comma-separated values supplied in the tag
AccountID int `faker:"oneof: 15, 27, 61"` // use commas to separate the values for now. Future support for other separator characters may be added
}

func Example_withTags() {
Expand Down Expand Up @@ -104,6 +106,8 @@ func Example_withTags() {
AmountWithCurrency: XBB 49257.100000,
UUIDHypenated: 8f8e4463-9560-4a38-9b0c-ef24481e4e27,
UUID: 90ea6479fd0e4940af741f0a87596b73,
PaymentMethod: paypal,
AccountID: 61
Skip:
}
*/
Expand Down
84 changes: 67 additions & 17 deletions faker.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ const (
BoundaryEnd = "boundary_end"
Equals = "="
comma = ","
colon = ":"
Copy link
Owner

@bxcodec bxcodec Jun 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any reasons why we choose colon for separator?

From the issue, he expects some kind of foo,bar,x,y I'm okay with either, but it will be great if we also considering the user experience?

How usually people separate a list of items? 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I thought about that. I can switch it if you want. I basically was lazy and didn't want to do the work to make oneof: value1, value2, value3. I didn't have an actually good reason for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to run an errand for a little bit right now. But when I get back in an hour or 2, I will be happy to make the user experience better.
i.e. support instead oneof: value1, value2, value <- use colon for the oneof tag only. Then use commas to actually separate the "arguments" to the tag.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bxcodec I think I have it in place now. It now uses , to separate the "arguments" to the oneof tag

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, thanks bro @andrew-werdna

ONEOF = "oneof"
// period = "."
)

var defaultTag = map[string]string{
Expand Down Expand Up @@ -233,6 +236,9 @@ var (
ErrWrongFormattedTag = "Tag \"%s\" is not written properly"
ErrUnknownType = "Unknown Type"
ErrNotSupportedTypeForTag = "Type is not supported by tag."
ErrUnsupportedTagArguments = "Tag arguments are not compatible with field type."
ErrDuplicateSeparator = "Duplicate separator for tag arguments."
ErrNotEnoughTagArguments = "Not enough arguments for tag."
)

// Compiled regexp
Expand Down Expand Up @@ -636,21 +642,6 @@ func setDataWithTag(v reflect.Value, tag string) error {
reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
return userDefinedNumber(v, tag)
case reflect.Slice, reflect.Array:
/**
* check for added Provider tag first before
* defaulting to userDefinedArray()
* this way the user at least has the
* option of a custom tag working
*/
_, tagExists := mapperTag[tag]
if tagExists {
res, err := mapperTag[tag](v)
if err != nil {
return err
}
v.Set(reflect.ValueOf(res))
return nil
}
return userDefinedArray(v, tag)
case reflect.Map:
return userDefinedMap(v, tag)
Expand Down Expand Up @@ -720,6 +711,15 @@ func getValueWithTag(t reflect.Type, tag string) (interface{}, error) {
}

func userDefinedArray(v reflect.Value, tag string) error {
_, tagExists := mapperTag[tag]
if tagExists {
res, err := mapperTag[tag](v)
if err != nil {
return err
}
v.Set(reflect.ValueOf(res))
return nil
}
len := randomSliceAndMapSize()
if shouldSetNil && len == 0 {
v.Set(reflect.Zero(v.Type()))
Expand Down Expand Up @@ -787,7 +787,8 @@ func extractStringFromTag(tag string) (interface{}, error) {
var err error
strlen := randomStringLen
strlng := &lang
if !strings.Contains(tag, Length) && !strings.Contains(tag, Language) {
isOneOfTag := strings.Contains(tag, ONEOF)
if !strings.Contains(tag, Length) && !strings.Contains(tag, Language) && !isOneOfTag {
return nil, fmt.Errorf(ErrTagNotSupported, tag)
}
if strings.Contains(tag, Length) {
Expand All @@ -803,6 +804,22 @@ func extractStringFromTag(tag string) (interface{}, error) {
return nil, fmt.Errorf(ErrWrongFormattedTag, tag)
}
}
if isOneOfTag {
items := strings.Split(tag, colon)
argsList := items[1:]
if len(argsList) != 1 {
return nil, fmt.Errorf(ErrUnsupportedTagArguments)
}
if strings.Contains(argsList[0], ",,") {
return nil, fmt.Errorf(ErrDuplicateSeparator)
}
args := strings.Split(argsList[0], comma)
if len(args) < 2 {
return nil, fmt.Errorf(ErrNotEnoughTagArguments)
}
toRet := args[rand.Intn(len(args))]
return strings.TrimSpace(toRet), nil
}
res := randomString(strlen, strlng)
return res, nil
}
Expand All @@ -826,9 +843,42 @@ func extractLangFromTag(tag string) (*langRuneBoundary, error) {
}

func extractNumberFromTag(tag string, t reflect.Type) (interface{}, error) {
if !strings.Contains(tag, BoundaryStart) || !strings.Contains(tag, BoundaryEnd) {
hasOneOf := strings.Contains(tag, ONEOF)
hasBoundaryStart := strings.Contains(tag, BoundaryStart)
hasBoundaryEnd := strings.Contains(tag, BoundaryEnd)
usingOneOfTag := hasOneOf && (!hasBoundaryStart && !hasBoundaryEnd)
usingBoundariesTags := !hasOneOf && (hasBoundaryStart && hasBoundaryEnd)
if !usingOneOfTag && !usingBoundariesTags {
return nil, fmt.Errorf(ErrTagNotSupported, tag)
}

// handling oneof tag
if usingOneOfTag {
argsList := strings.Split(tag, colon)[1:]
if len(argsList) != 1 {
return nil, fmt.Errorf(ErrUnsupportedTagArguments)
}
if strings.Contains(argsList[0], ",,") {
return nil, fmt.Errorf(ErrDuplicateSeparator)
}
args := strings.Split(argsList[0], comma)
if len(args) < 2 {
return nil, fmt.Errorf(ErrNotEnoughTagArguments)
}
var numberValues []int
for _, i := range args {
k := strings.TrimSpace(i)
j, err := strconv.Atoi(k)
if err != nil {
return nil, fmt.Errorf(ErrUnsupportedTagArguments)
}
numberValues = append(numberValues, j)
}
toRet := numberValues[rand.Intn(len(numberValues))]
return toRet, nil
}

// handling boundary tags
valuesStr := strings.SplitN(tag, comma, -1)
if len(valuesStr) != 2 {
return nil, fmt.Errorf(ErrWrongFormattedTag, tag)
Expand Down
Loading