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

Commit

Permalink
feat: Implement oneof tag (#103)
Browse files Browse the repository at this point in the history
* move custom tag check to inside userDefinedArray (following similar pattern to the other userDefined<type> functions); rearrange and add tests;

* go fmt fixes; add support for 'oneof' tag for strings; add one basic test;

* add error check and error return for malformed tag; add extra tests to validate these scenarios

* added support for a tag called oneof for strings and ints

* update the creation of the custom types (not using the underlying types)

* update examples; add const for convenience

* go fmt

* update documentation examples; add TrimSpace to ensure space between colon separated values doesn't matter;

* update readme with caveat about 'oneof' tag

* linter fixes

* clean up duplicate if conditions

* finish fixing merge conflicts

* update example to be correct. use ':' to separate arguments not ','

* update example comments for clarity

* ensure comma separated arguments to oneof tag; add several more tests and a new error;

* update documentation examples

* remove useless if statement that is never true

Co-authored-by: altair <[email protected]>
  • Loading branch information
andrew-werdna and andrew-werdna authored Jun 8, 2020
1 parent 492cb34 commit 463a71e
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 33 deletions.
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 = ":"
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

0 comments on commit 463a71e

Please sign in to comment.