Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
brianvoe committed Apr 14, 2023
2 parents 13a17db + 272cff4 commit 7bb65c6
Show file tree
Hide file tree
Showing 16 changed files with 2,381 additions and 346 deletions.
628 changes: 455 additions & 173 deletions BENCHMARKS.md

Large diffs are not rendered by default.

51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,47 @@ fmt.Println(f.Created.String()) // 1908-12-07 04:14:25.685339029 +0000 UTC
// Nested Struct Fields and Embedded Fields
```

## Fakeable types

It is possible to extend a struct by implementing the `Fakeable` interface
in order to control the generation.

For example, this is useful when it is not possible to modify the struct that you want to fake by adding struct tags to a field but you still need to be able to control the generation process.

```go
// Custom string that you want to generate your own data for
// or just return a static value
type CustomString string

func (c *CustomString) Fake(faker *gofakeit.Faker) interface{} {
return CustomString("my custom string")
}

// Imagine a CustomTime type that is needed to support a custom JSON Marshaler
type CustomTime time.Time

func (c *CustomTime) Fake(faker *gofakeit.Faker) interface{} {
return CustomTime(time.Now())
}

func (c *CustomTime) MarshalJSON() ([]byte, error) {
//...
}

// This is the struct that we cannot modify to add struct tags
type NotModifiable struct {
Token string
Value CustomString
Creation *CustomTime
}

var f NotModifiable
gofakeit.Struct(&f)
fmt.Printf("%s", f.Token) // yvqqdH
fmt.Printf("%s", f.Value) // my custom string
fmt.Printf("%s", f.Creation) // 2023-04-02 23:00:00 +0000 UTC m=+0.000000001
```

## Custom Functions

In a lot of situations you may need to use your own random function usage for your specific needs.
Expand Down Expand Up @@ -226,7 +267,10 @@ All functions also exist as methods on the Faker struct

### File

Passing `nil` to `CSV`, `JSON` or `XML` it will auto generate data using a random set of generators.

```go
CSV(co *CSVOptions) ([]byte, error)
JSON(jo *JSONOptions) ([]byte, error)
XML(xo *XMLOptions) ([]byte, error)
FileExtension() string
Expand Down Expand Up @@ -533,6 +577,13 @@ BitcoinAddress() string
BitcoinPrivateKey() string
```

### Finance

```go
Cusip() string
Isin() string
```

### Company

```go
Expand Down
27 changes: 19 additions & 8 deletions csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,28 @@ import (

// CSVOptions defines values needed for csv generation
type CSVOptions struct {
Delimiter string `json:"delimiter" xml:"delimiter"`
RowCount int `json:"row_count" xml:"row_count"`
Fields []Field `json:"fields" xml:"fields"`
Delimiter string `json:"delimiter" xml:"delimiter" fake:"{randomstring:[,,tab]}"`
RowCount int `json:"row_count" xml:"row_count" fake:"{number:1,10}"`
Fields []Field `json:"fields" xml:"fields" fake:"{internal_exampleFields}"`
}

// CSV generates an object or an array of objects in json format
func CSV(co *CSVOptions) ([]byte, error) { return csvFunc(globalFaker.Rand, co) }
// A nil CSVOptions returns a randomly structured CSV.
func CSV(co *CSVOptions) ([]byte, error) { return csvFunc(globalFaker, co) }

// CSV generates an object or an array of objects in json format
func (f *Faker) CSV(co *CSVOptions) ([]byte, error) { return csvFunc(f.Rand, co) }
// A nil CSVOptions returns a randomly structured CSV.
func (f *Faker) CSV(co *CSVOptions) ([]byte, error) { return csvFunc(f, co) }

func csvFunc(f *Faker, co *CSVOptions) ([]byte, error) {
if co == nil {
// We didn't get a CSVOptions, so create a new random one
err := f.Struct(&co)
if err != nil {
return nil, err
}
}

func csvFunc(r *rand.Rand, co *CSVOptions) ([]byte, error) {
// Check delimiter
if co.Delimiter == "" {
co.Delimiter = ","
Expand Down Expand Up @@ -74,7 +84,7 @@ func csvFunc(r *rand.Rand, co *CSVOptions) ([]byte, error) {
return nil, errors.New("invalid function, " + field.Function + " does not exist")
}

value, err := funcInfo.Generate(r, &field.Params, funcInfo)
value, err := funcInfo.Generate(f.Rand, &field.Params, funcInfo)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -165,7 +175,8 @@ func addFileCSVLookup() {
}
co.Delimiter = delimiter

csvOut, err := csvFunc(r, &co)
f := &Faker{Rand: r}
csvOut, err := csvFunc(f, &co)
if err != nil {
return nil, err
}
Expand Down
10 changes: 10 additions & 0 deletions csv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@ func TestCSVLookup(t *testing.T) {
// t.Fatal(fmt.Sprintf("%s", value.([]byte)))
}

func TestCSVNoOptions(t *testing.T) {
Seed(11)

// if CSVOptions is nil -> get a random CSVOptions
_, err := CSV(nil)
if err != nil {
t.Fatal(err.Error())
}
}

func BenchmarkCSVLookup100(b *testing.B) {
faker := New(0)

Expand Down
80 changes: 80 additions & 0 deletions fakeable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package gofakeit

import (
"errors"
"fmt"
"reflect"
)

// Fakeable is an interface that can be implemented by a type to provide a custom fake value.
type Fakeable interface {
// Fake returns a fake value for the type.
Fake(faker *Faker) interface{}
}

func isFakeable(t reflect.Type) bool {
fakeableTyp := reflect.TypeOf((*Fakeable)(nil)).Elem()

return t.Implements(fakeableTyp) || reflect.PtrTo(t).Implements(fakeableTyp)
}

func callFake(faker *Faker, v reflect.Value, possibleKinds ...reflect.Kind) (interface{}, error) {
f, ok := v.Addr().Interface().(Fakeable)
if !ok {
return nil, errors.New("not a Fakeable type")
}

fakedValue := f.Fake(faker)
k := reflect.TypeOf(fakedValue).Kind()
if !containsKind(possibleKinds, k) {
return nil, fmt.Errorf("returned value kind %q is not amongst the valid ones: %v", k, possibleKinds)
}

switch k {
case reflect.String:
return reflect.ValueOf(fakedValue).String(), nil
case reflect.Bool:
return reflect.ValueOf(fakedValue).Bool(), nil
case reflect.Int:
return int(reflect.ValueOf(fakedValue).Int()), nil
case reflect.Int8:
return int8(reflect.ValueOf(fakedValue).Int()), nil
case reflect.Int16:
return int16(reflect.ValueOf(fakedValue).Int()), nil
case reflect.Int32:
return int32(reflect.ValueOf(fakedValue).Int()), nil
case reflect.Int64:
return int64(reflect.ValueOf(fakedValue).Int()), nil
case reflect.Uint:
return uint(reflect.ValueOf(fakedValue).Uint()), nil
case reflect.Uint8:
return uint8(reflect.ValueOf(fakedValue).Uint()), nil
case reflect.Uint16:
return uint16(reflect.ValueOf(fakedValue).Uint()), nil
case reflect.Uint32:
return uint32(reflect.ValueOf(fakedValue).Uint()), nil
case reflect.Uint64:
return uint64(reflect.ValueOf(fakedValue).Uint()), nil
case reflect.Float32:
return float32(reflect.ValueOf(fakedValue).Float()), nil
case reflect.Float64:
return float64(reflect.ValueOf(fakedValue).Float()), nil
case reflect.Slice:
return reflect.ValueOf(fakedValue).Interface(), nil
case reflect.Map:
return reflect.ValueOf(fakedValue).Interface(), nil
case reflect.Struct:
return reflect.ValueOf(fakedValue).Interface(), nil
default:
return nil, fmt.Errorf("unsupported type %q", k)
}
}

func containsKind(possibleKinds []reflect.Kind, kind reflect.Kind) bool {
for _, k := range possibleKinds {
if k == kind {
return true
}
}
return false
}
Loading

0 comments on commit 7bb65c6

Please sign in to comment.