Repository: bxcodec/faker
Pull request #156 (2022-02-02 18:18:08)

Float boundaries
Hey, @bxcodec.
I have no idea why float32 and float64 isn't support boundaries tags. This is why I made this PR.

What is reason of that this isn't made before? I want to discuss it cause I can don't realize some fatal reasons of this. Please, take a look.

What made by me:
* added ability to set boundaries for float32 and float64 types;
* rename old methods which supports only integers to more explicit;
* update tests;
* delete test which checks float to unsupported struct to boundaries.


Patch:
diff --git a/faker.go b/faker.go
index 4da208d..31e684a 100644
--- a/faker.go
+++ b/faker.go
@@ -25,8 +25,10 @@ var (
 	testRandZero = false
 	//Sets the default number of string when it is created randomly.
 	randomStringLen = 25
-	//Sets the boundary for random value generation. Boundaries can not exceed integer(4 byte...)
-	nBoundary = numberBoundary{start: 0, end: 100}
+	//Sets the boundary for random integer value generation. Boundaries can not exceed integer(4 byte...)
+	iBoundary = intBoundary{start: 0, end: 100}
+	//Sets the boundary for random float value generation. Boundaries should comply with float values constraints (IEEE 754)
+	fBoundary = floatBoundary{start: 0, end: 100}
 	//Sets the random max size for slices and maps.
 	randomMaxSize = 100
 	//Sets the random min size for slices and maps.
@@ -43,11 +45,16 @@ var (
 	maxGenerateStringRetries = 1000000
 )
 
-type numberBoundary struct {
+type intBoundary struct {
 	start int
 	end   int
 }
 
+type floatBoundary struct {
+	start float64
+	end   float64
+}
+
 type langRuneBoundary struct {
 	start   rune
 	end     rune
@@ -366,7 +373,7 @@ func SetRandomNumberBoundaries(start, end int) error {
 	if start > end {
 		return errors.New(ErrStartValueBiggerThanEnd)
 	}
-	nBoundary = numberBoundary{start: start, end: end}
+	iBoundary = intBoundary{start: start, end: end}
 	return nil
 }
 
@@ -596,9 +603,9 @@ func getValue(a interface{}) (reflect.Value, error) {
 	case reflect.Int64:
 		return reflect.ValueOf(int64(randomInteger())), nil
 	case reflect.Float32:
-		return reflect.ValueOf(rand.Float32()), nil
+		return reflect.ValueOf(float32(randomFloat())), nil
 	case reflect.Float64:
-		return reflect.ValueOf(rand.Float64()), nil
+		return reflect.ValueOf(randomFloat()), nil
 	case reflect.Bool:
 		val := rand.Intn(2) > 0
 		return reflect.ValueOf(val), nil
@@ -799,7 +806,7 @@ func userDefinedMap(v reflect.Value, tag string) error {
 func getValueWithTag(t reflect.Type, tag string) (interface{}, error) {
 	switch t.Kind() {
 	case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int8, reflect.Int16, reflect.Uint, reflect.Uint8,
-		reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
 		res, err := extractNumberFromTag(tag, t)
 		if err != nil {
 			return nil, err
@@ -1095,15 +1102,35 @@ func extractNumberFromTag(tag string, t reflect.Type) (interface{}, error) {
 	if len(valuesStr) != 2 {
 		return nil, fmt.Errorf(ErrWrongFormattedTag, tag)
 	}
-	startBoundary, err := extractNumberFromText(valuesStr[0])
+
+	// TODO(Xaspy): When Golang provides generics, we will be able to make this method simpler and more beautiful.
+	if t.Kind() == reflect.Float64 || t.Kind() == reflect.Float32 {
+		startBoundary, err := extractFloatFromText(valuesStr[0])
+		if err != nil {
+			return nil, err
+		}
+		endBoundary, err := extractFloatFromText(valuesStr[1])
+		if err != nil {
+			return nil, err
+		}
+		boundary := floatBoundary{start: startBoundary, end: endBoundary}
+		switch t.Kind() {
+		case reflect.Float32:
+			return float32(randomFloatWithBoundary(boundary)), nil
+		case reflect.Float64:
+			return randomFloatWithBoundary(boundary), nil
+		}
+	}
+
+	startBoundary, err := extractIntFromText(valuesStr[0])
 	if err != nil {
 		return nil, err
 	}
-	endBoundary, err := extractNumberFromText(valuesStr[1])
+	endBoundary, err := extractIntFromText(valuesStr[1])
 	if err != nil {
 		return nil, err
 	}
-	boundary := numberBoundary{start: startBoundary, end: endBoundary}
+	boundary := intBoundary{start: startBoundary, end: endBoundary}
 	switch t.Kind() {
 	case reflect.Uint:
 		return uint(randomIntegerWithBoundary(boundary)), nil
@@ -1130,7 +1157,7 @@ func extractNumberFromTag(tag string, t reflect.Type) (interface{}, error) {
 	}
 }
 
-func extractNumberFromText(text string) (int, error) {
+func extractIntFromText(text string) (int, error) {
 	text = strings.TrimSpace(text)
 	texts := strings.SplitN(text, Equals, -1)
 	if len(texts) != 2 {
@@ -1139,6 +1166,15 @@ func extractNumberFromText(text string) (int, error) {
 	return strconv.Atoi(texts[1])
 }
 
+func extractFloatFromText(text string) (float64, error) {
+	text = strings.TrimSpace(text)
+	texts := strings.SplitN(text, Equals, -1)
+	if len(texts) != 2 {
+		return 0, fmt.Errorf(ErrWrongFormattedTag, text)
+	}
+	return strconv.ParseFloat(texts[1], 64)
+}
+
 func fetchOneOfArgsFromTag(tag string) ([]string, error) {
 	items := strings.Split(tag, colon)
 	argsList := items[1:]
@@ -1189,7 +1225,7 @@ func randomString(n int, lang *langRuneBoundary) (string, error) {
 }
 
 // randomIntegerWithBoundary returns a random integer between input start and end boundary. [start, end)
-func randomIntegerWithBoundary(boundary numberBoundary) int {
+func randomIntegerWithBoundary(boundary intBoundary) int {
 	span := boundary.end - boundary.start
 	if span <= 0 {
 		return boundary.start
@@ -1197,9 +1233,23 @@ func randomIntegerWithBoundary(boundary numberBoundary) int {
 	return rand.Intn(span) + boundary.start
 }
 
+// randomFloatWithBoundary returns a random float between input start and end boundary. [start, end)
+func randomFloatWithBoundary(boundary floatBoundary) float64 {
+	span := boundary.end - boundary.start
+	if span <= 0 {
+		return boundary.start
+	}
+	return boundary.start + rand.Float64()*span
+}
+
 // randomInteger returns a random integer between start and end boundary. [start, end)
 func randomInteger() int {
-	return randomIntegerWithBoundary(nBoundary)
+	return randomIntegerWithBoundary(iBoundary)
+}
+
+// randomFloat returns a random float between start and end boundary. [start, end)
+func randomFloat() float64 {
+	return randomFloatWithBoundary(fBoundary)
 }
 
 // randomSliceAndMapSize returns a random integer between [0,randomSliceAndMapSize). If the testRandZero is set, returns 0


Test patch:
diff --git a/example_with_tags_lenbounds_test.go b/example_with_tags_lenbounds_test.go
index 8ada652..2b71d53 100644
--- a/example_with_tags_lenbounds_test.go
+++ b/example_with_tags_lenbounds_test.go
@@ -22,6 +22,9 @@ func Example_withTagsLengthAndBoundary() {
 		UInt32 uint32 `faker:"boundary_start=0, boundary_end=40"`
 		UInt64 uint64 `faker:"boundary_start=14, boundary_end=50"`
 
+		Float32 float32 `faker:"boundary_start=12.65, boundary_end=184.05"`
+		Float64 float64 `faker:"boundary_start=1.256, boundary_end=3.4"`
+
 		ASString []string          `faker:"len=50"`
 		SString  string            `faker:"len=25"`
 		MSString map[string]string `faker:"len=30"`
@@ -45,6 +48,8 @@ func Example_withTagsLengthAndBoundary() {
 	       UInt16:1797
 	       UInt32:8
 	       UInt64:34
+	       Float32:60.999058
+	       Float64:2.590148738554016
 	       ASString:[
 	           geHYIpEoQhQdijFooVEAOyvtTwJOofbQPJdbHvEEdjueZaKIgI
 	           WVJBBtmrrVccyIydAiLSkMwWbFzFMEotEXsyUXqcmBTVORlkJK
diff --git a/faker_test.go b/faker_test.go
index c86f63b..43af040 100644
--- a/faker_test.go
+++ b/faker_test.go
@@ -131,6 +131,9 @@ type SomeStructWithLen struct {
 	UInt32 uint32 `faker:"boundary_start=5, boundary_end=10"`
 	UInt64 uint64 `faker:"boundary_start=5, boundary_end=10"`
 
+	Float32 float32 `faker:"boundary_start=5, boundary_end=10"`
+	Float64 float64 `faker:"boundary_start=5, boundary_end=10"`
+
 	ASString []string          `faker:"len=2"`
 	SString  string            `faker:"len=2"`
 	MSString map[string]string `faker:"len=2"`
@@ -485,7 +488,7 @@ func TestSetRandomNumberBoundaries(t *testing.T) {
 	if err := SetRandomNumberBoundaries(10, 0); err == nil {
 		t.Error("Start must be smaller than end value")
 	}
-	boundary := numberBoundary{start: 10, end: 90}
+	boundary := intBoundary{start: 10, end: 90}
 	if err := SetRandomNumberBoundaries(boundary.start, boundary.end); err != nil {
 		t.Error("SetRandomNumberBoundaries method is corrupted.")
 	}
@@ -551,34 +554,40 @@ func TestBoundaryAndLen(t *testing.T) {
 		if err := FakeData(&someStruct); err != nil {
 			t.Error(err)
 		}
-		if err := validateRange(int(someStruct.Int8)); err != nil {
+		if err := validateIntRange(int(someStruct.Int8)); err != nil {
+			t.Error(err)
+		}
+		if err := validateIntRange(int(someStruct.Int16)); err != nil {
 			t.Error(err)
 		}
-		if err := validateRange(int(someStruct.Int16)); err != nil {
+		if err := validateIntRange(int(someStruct.Int32)); err != nil {
 			t.Error(err)
 		}
-		if err := validateRange(int(someStruct.Int32)); err != nil {
+		if err := validateIntRange(someStruct.Inta); err != nil {
 			t.Error(err)
 		}
-		if err := validateRange(someStruct.Inta); err != nil {
+		if err := validateIntRange(int(someStruct.Int64)); err != nil {
 			t.Error(err)
 		}
-		if err := validateRange(int(someStruct.Int64)); err != nil {
+		if err := validateIntRange(int(someStruct.UInt8)); err != nil {
 			t.Error(err)
 		}
-		if err := validateRange(int(someStruct.UInt8)); err != nil {
+		if err := validateIntRange(int(someStruct.UInt16)); err != nil {
 			t.Error(err)
 		}
-		if err := validateRange(int(someStruct.UInt16)); err != nil {
+		if err := validateIntRange(int(someStruct.UInt32)); err != nil {
 			t.Error(err)
 		}
-		if err := validateRange(int(someStruct.UInt32)); err != nil {
+		if err := validateIntRange(int(someStruct.UInta)); err != nil {
 			t.Error(err)
 		}
-		if err := validateRange(int(someStruct.UInta)); err != nil {
+		if err := validateIntRange(int(someStruct.UInt64)); err != nil {
 			t.Error(err)
 		}
-		if err := validateRange(int(someStruct.UInt64)); err != nil {
+		if err := validateFloatRange(float64(someStruct.Float32)); err != nil {
+			t.Error(err)
+		}
+		if err := validateFloatRange(someStruct.Float64); err != nil {
 			t.Error(err)
 		}
 		if err := validateLen(someStruct.SString); err != nil {
@@ -598,10 +607,10 @@ func TestBoundaryAndLen(t *testing.T) {
 			}
 		}
 		for k, v := range someStruct.MIint {
-			if err := validateRange(k); err != nil {
+			if err := validateIntRange(k); err != nil {
 				t.Error(err)
 			}
-			if err := validateRange(v); err != nil {
+			if err := validateIntRange(v); err != nil {
 				t.Error(err)
 			}
 		}
@@ -823,12 +832,6 @@ func isStringLangCorrect(value string, lang langRuneBoundary) error {
 }
 
 func TestExtractNumberFromTagFail(t *testing.T) {
-	notSupportedTypeStruct := &struct {
-		Test float32 `faker:"boundary_start=5, boundary_end=10"`
-	}{}
-	if err := FakeData(&notSupportedTypeStruct); err == nil {
-		t.Error(err)
-	}
 	notSupportedStruct := &struct {
 		Test int `faker:"boundary_start=5"`
 	}{}
@@ -877,7 +880,7 @@ func validateLen(value string) error {
 	return nil
 }
 
-func validateRange(value int) error {
+func validateIntRange(value int) error {
 	if value < someStructBoundaryStart || value > someStructBoundaryEnd {
 		return fmt.Errorf("%d must be between %d and %d", value, someStructBoundaryStart,
 			someStructBoundaryEnd)
@@ -885,6 +888,14 @@ func validateRange(value int) error {
 	return nil
 }
 
+func validateFloatRange(value float64) error {
+	if value < someStructBoundaryStart || value > someStructBoundaryEnd {
+		return fmt.Errorf("%f must be between %d and %d", value, someStructBoundaryStart,
+			someStructBoundaryEnd)
+	}
+	return nil
+}
+
 func TestSetDataWithTagIfFirstArgumentNotPtr(t *testing.T) {
 	temp := struct{}{}
 	if setDataWithTag(reflect.ValueOf(temp), "").Error() != "Not a pointer value" {